@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,1360 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tests/fm-backlog-handoff.test.sh - full item-block handoff (header + indented body).
|
|
3
|
+
#
|
|
4
|
+
# The happy single-line path and broad safety refusals live in the secondmate
|
|
5
|
+
# lifecycle and safety suites. This file owns focused delegated-handoff
|
|
6
|
+
# regressions: the multi-line body contract and registry home parsing edge cases.
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
# shellcheck source=tests/secondmate-helpers.sh disable=SC1091
|
|
10
|
+
. "$(dirname "${BASH_SOURCE[0]}")/secondmate-helpers.sh"
|
|
11
|
+
|
|
12
|
+
# The move is delegated to `tasks-axi mv`, so this suite exercises the real
|
|
13
|
+
# binary. Skip cleanly when it is absent (matching the backend smoke suites).
|
|
14
|
+
command -v tasks-axi >/dev/null 2>&1 || { echo "skip: tasks-axi not found (required by the delegated handoff path)"; exit 0; }
|
|
15
|
+
|
|
16
|
+
TMP_ROOT=$(fm_test_tmproot fm-backlog-handoff)
|
|
17
|
+
HANDOFF_FAKEBIN=$(make_fake_tmux "$TMP_ROOT/default-fake")
|
|
18
|
+
export PATH="$HANDOFF_FAKEBIN:$PATH"
|
|
19
|
+
export FM_FAKE_TMUX_WINDOW='firstmate:fm-design'
|
|
20
|
+
export FM_FAKE_TMUX_LOG="$TMP_ROOT/default-tmux.log"
|
|
21
|
+
export FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/default-fake/pane.txt"
|
|
22
|
+
export FM_SEND_SETTLE=0 FM_SEND_SLEEP=0 FM_SEND_RETRIES=1
|
|
23
|
+
|
|
24
|
+
setup_homes() {
|
|
25
|
+
local home=$1 subhome=$2 id=${3:-design}
|
|
26
|
+
mkdir -p "$home/data" "$home/state"
|
|
27
|
+
seed_secondmate_home_marker "$subhome" "$id"
|
|
28
|
+
local sub_abs
|
|
29
|
+
sub_abs=$(cd "$subhome" && pwd -P)
|
|
30
|
+
printf -- '- %s - feature work (home: %s; scope: feature work; projects: alpha; added 2026-07-09)\n' \
|
|
31
|
+
"$id" "$sub_abs" > "$home/data/secondmates.md"
|
|
32
|
+
cat > "$home/state/$id.meta" <<EOF
|
|
33
|
+
window=firstmate:fm-$id
|
|
34
|
+
kind=secondmate
|
|
35
|
+
harness=claude
|
|
36
|
+
backend=tmux
|
|
37
|
+
home=$sub_abs
|
|
38
|
+
worktree=$sub_abs
|
|
39
|
+
EOF
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
inbox_body_stream() { # <state-dir> <task-id>
|
|
43
|
+
local rec
|
|
44
|
+
for rec in "$1/$2.inbox"/*.msg; do
|
|
45
|
+
[ -f "$rec" ] || continue
|
|
46
|
+
bash -c '. "$1"; fm_task_inbox_body "$2"' _ \
|
|
47
|
+
"$ROOT/bin/fm-task-inbox-lib.sh" "$rec"
|
|
48
|
+
done
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
inbox_record_count() { # <state-dir> <task-id>
|
|
52
|
+
find "$1/$2.inbox" -maxdepth 1 -type f -name '*.msg' 2>/dev/null | wc -l | tr -d '[:space:]'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
doorbell_count() { # <backend-log>
|
|
56
|
+
grep -cF 'Firstmate instruction waiting:' "$1" 2>/dev/null || true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# A live local receiver gets the routed-work instruction through its durable
|
|
60
|
+
# inbox record while the endpoint receives only the constant doorbell.
|
|
61
|
+
test_handoff_wakes_live_local_receiver() {
|
|
62
|
+
local home="$TMP_ROOT/live-wake-main" sub="$TMP_ROOT/live-wake-sub" fakebin out wake_count
|
|
63
|
+
setup_homes "$home" "$sub"
|
|
64
|
+
mkdir -p "$sub/state" "$sub/data"
|
|
65
|
+
cat > "$home/state/design.meta" <<EOF
|
|
66
|
+
window=firstmate:fm-design
|
|
67
|
+
kind=secondmate
|
|
68
|
+
harness=claude
|
|
69
|
+
backend=tmux
|
|
70
|
+
home=$sub
|
|
71
|
+
worktree=$sub
|
|
72
|
+
EOF
|
|
73
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
74
|
+
## Queued
|
|
75
|
+
- [ ] wake-item - routed to a live receiver (repo: alpha)
|
|
76
|
+
|
|
77
|
+
## Done
|
|
78
|
+
EOF
|
|
79
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
80
|
+
fakebin=$(make_fake_tmux "$TMP_ROOT/live-wake-fake")
|
|
81
|
+
out="$TMP_ROOT/live-wake.out"
|
|
82
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" PATH="$fakebin:$PATH" \
|
|
83
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
84
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/live-wake-tmux.log" \
|
|
85
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/live-wake-fake/pane.txt" \
|
|
86
|
+
FM_SEND_SETTLE=0 FM_SEND_SLEEP=0 FM_SEND_RETRIES=1 \
|
|
87
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design wake-item > "$out" 2>&1 \
|
|
88
|
+
|| fail "handoff to a live receiver failed: $(cat "$out")"
|
|
89
|
+
grep -F 'wake-item' "$sub/data/backlog.md" >/dev/null \
|
|
90
|
+
|| fail "live receiver did not receive the routed backlog item"
|
|
91
|
+
grep -F 'send-keys' "$TMP_ROOT/live-wake-tmux.log" >/dev/null \
|
|
92
|
+
|| fail "handoff did not ring the live receiver endpoint"
|
|
93
|
+
assert_contains "$(inbox_body_stream "$home/state" design)" \
|
|
94
|
+
'New routed work is in your backlog.' \
|
|
95
|
+
"receiver inbox did not carry the routed-work instruction"
|
|
96
|
+
wake_count=$(inbox_record_count "$home/state" design)
|
|
97
|
+
[ "$(doorbell_count "$TMP_ROOT/live-wake-tmux.log")" -eq 1 ] \
|
|
98
|
+
|| fail "handoff did not ring exactly one constant receiver doorbell"
|
|
99
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" PATH="$fakebin:$PATH" \
|
|
100
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
101
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/live-wake-tmux.log" \
|
|
102
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/live-wake-fake/pane.txt" \
|
|
103
|
+
FM_SEND_SETTLE=0 FM_SEND_SLEEP=0 FM_SEND_RETRIES=1 \
|
|
104
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design wake-item > "$TMP_ROOT/live-wake-rerun.out" 2>&1 \
|
|
105
|
+
|| fail "idempotent successful handoff rerun failed: $(cat "$TMP_ROOT/live-wake-rerun.out")"
|
|
106
|
+
[ "$(inbox_record_count "$home/state" design)" -eq "$wake_count" ] \
|
|
107
|
+
|| fail "idempotent successful handoff rerun duplicated the receiver inbox record"
|
|
108
|
+
[ "$(doorbell_count "$TMP_ROOT/live-wake-tmux.log")" -eq 1 ] \
|
|
109
|
+
|| fail "idempotent successful handoff rerun duplicated the receiver doorbell"
|
|
110
|
+
pass "a routed handoff wakes once and a successful rerun stays idempotent"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
test_failed_wake_retries_when_the_item_is_already_present() {
|
|
114
|
+
local home="$TMP_ROOT/retry-wake-main" sub="$TMP_ROOT/retry-wake-sub" out corr rc=0
|
|
115
|
+
setup_homes "$home" "$sub"
|
|
116
|
+
rm -f "$home/state/design.meta"
|
|
117
|
+
mkdir -p "$sub/data"
|
|
118
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
119
|
+
## Queued
|
|
120
|
+
- [ ] retry-item - wake must be retried (repo: alpha)
|
|
121
|
+
|
|
122
|
+
## Done
|
|
123
|
+
EOF
|
|
124
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
125
|
+
|
|
126
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design retry-item 2>&1) || rc=$?
|
|
127
|
+
[ "$rc" -ne 0 ] || fail "handoff without a receiver endpoint reported success"
|
|
128
|
+
assert_contains "$out" "receiver was not woken" "missing receiver failure was not observable"
|
|
129
|
+
assert_grep 'retry-item' "$sub/data/backlog.md" "failed wake lost the durably handed-off item"
|
|
130
|
+
corr=$(cut -d: -f2- "$home/state/.backlog-handoff-design.wake-pending")
|
|
131
|
+
assert_absent "$home/state/pending-replies/.delivery-confirmed-$corr" \
|
|
132
|
+
"missing endpoint was recorded as an attempted delivery"
|
|
133
|
+
|
|
134
|
+
cat > "$home/state/design.meta" <<EOF
|
|
135
|
+
window=firstmate:fm-design
|
|
136
|
+
kind=secondmate
|
|
137
|
+
harness=claude
|
|
138
|
+
backend=tmux
|
|
139
|
+
home=$sub
|
|
140
|
+
worktree=$sub
|
|
141
|
+
EOF
|
|
142
|
+
: > "$TMP_ROOT/default-tmux.log"
|
|
143
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design retry-item > "$TMP_ROOT/retry-wake.out" 2>&1 \
|
|
144
|
+
|| fail "an already-present handoff did not retry its receiver wake: $(cat "$TMP_ROOT/retry-wake.out")"
|
|
145
|
+
assert_contains "$(inbox_body_stream "$home/state" design)" \
|
|
146
|
+
'New routed work is in your backlog.' \
|
|
147
|
+
"the recovery handoff did not enqueue the receiver instruction"
|
|
148
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 1 ] \
|
|
149
|
+
|| fail "the recovery handoff did not ring the receiver doorbell"
|
|
150
|
+
pass "a failed receiver wake is loud and retries from an already-present handoff"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
test_known_receiver_failure_remains_retryable_after_grace() {
|
|
154
|
+
local home="$TMP_ROOT/known-fail-main" sub="$TMP_ROOT/known-fail-sub"
|
|
155
|
+
local basebin rejectbin="$TMP_ROOT/known-fail-reject" out corr rec_count rc=0
|
|
156
|
+
setup_homes "$home" "$sub"
|
|
157
|
+
mkdir -p "$sub/data" "$rejectbin"
|
|
158
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
159
|
+
## Queued
|
|
160
|
+
- [ ] known-fail - retain delivery across a failed doorbell (repo: alpha)
|
|
161
|
+
|
|
162
|
+
## Done
|
|
163
|
+
EOF
|
|
164
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
165
|
+
basebin=$(make_fake_tmux "$TMP_ROOT/known-fail-fake")
|
|
166
|
+
cat > "$rejectbin/tmux" <<'SH'
|
|
167
|
+
#!/usr/bin/env bash
|
|
168
|
+
[ "${1:-}" != send-keys ] || exit 1
|
|
169
|
+
exec "$FM_BASE_TMUX" "$@"
|
|
170
|
+
SH
|
|
171
|
+
chmod +x "$rejectbin/tmux"
|
|
172
|
+
|
|
173
|
+
out=$(PATH="$rejectbin:$basebin:$PATH" FM_BASE_TMUX="$basebin/tmux" \
|
|
174
|
+
FM_HOME="$home" FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
175
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/known-fail-tmux.log" \
|
|
176
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/known-fail-fake/pane.txt" \
|
|
177
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design known-fail 2>&1) || rc=$?
|
|
178
|
+
[ "$rc" -eq 0 ] || fail "failed advisory doorbell negated durable handoff: $out"
|
|
179
|
+
assert_contains "$out" 'doorbell did not reach' \
|
|
180
|
+
"failed advisory doorbell was not reported"
|
|
181
|
+
assert_grep 'known-fail' "$sub/data/backlog.md" "failed doorbell lost the durable item"
|
|
182
|
+
assert_contains "$(inbox_body_stream "$home/state" design)" \
|
|
183
|
+
'New routed work is in your backlog.' "failed doorbell lost the durable receiver instruction"
|
|
184
|
+
rec_count=$(inbox_record_count "$home/state" design)
|
|
185
|
+
[ "$rec_count" -eq 1 ] || fail "failed doorbell produced $rec_count inbox records"
|
|
186
|
+
corr=$(grep -l '^task_id=design$' "$home/state/pending-replies"/* 2>/dev/null | head -n 1)
|
|
187
|
+
[ -n "$corr" ] || fail "durable receiver instruction lost its reply expectation"
|
|
188
|
+
[ -n "$(grep '^delivered_epoch=' "$corr" | cut -d= -f2-)" ] \
|
|
189
|
+
|| fail "durable enqueue was not recorded as delivered"
|
|
190
|
+
|
|
191
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design known-fail \
|
|
192
|
+
> "$TMP_ROOT/known-fail-retry.out" 2>&1 \
|
|
193
|
+
|| fail "idempotent handoff after failed doorbell failed: $(cat "$TMP_ROOT/known-fail-retry.out")"
|
|
194
|
+
[ "$(inbox_record_count "$home/state" design)" -eq "$rec_count" ] \
|
|
195
|
+
|| fail "retry duplicated a receiver instruction already delivered by inbox"
|
|
196
|
+
pass "a failed advisory doorbell leaves the inbox delivery successful and idempotent"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
test_known_failure_restores_retry_after_reconciliation_race() {
|
|
200
|
+
local home="$TMP_ROOT/reconcile-race-main" sub="$TMP_ROOT/reconcile-race-sub"
|
|
201
|
+
local basebin blockbin="$TMP_ROOT/reconcile-race-block" handoff i corr phase
|
|
202
|
+
setup_homes "$home" "$sub"
|
|
203
|
+
mkdir -p "$sub/data" "$blockbin"
|
|
204
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
205
|
+
## Queued
|
|
206
|
+
- [ ] reconcile-race - retry after concurrent reconciliation (repo: alpha)
|
|
207
|
+
|
|
208
|
+
## Done
|
|
209
|
+
EOF
|
|
210
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
211
|
+
basebin=$(make_fake_tmux "$TMP_ROOT/reconcile-race-fake")
|
|
212
|
+
cat > "$blockbin/tmux" <<'SH'
|
|
213
|
+
#!/usr/bin/env bash
|
|
214
|
+
if [ "${1:-}" = send-keys ]; then
|
|
215
|
+
touch "$FM_RECONCILE_RACE_ENTERED"
|
|
216
|
+
while [ ! -f "$FM_RECONCILE_RACE_RELEASE" ]; do sleep 0.02; done
|
|
217
|
+
exit 1
|
|
218
|
+
fi
|
|
219
|
+
exec "$FM_BASE_TMUX" "$@"
|
|
220
|
+
SH
|
|
221
|
+
chmod +x "$blockbin/tmux"
|
|
222
|
+
|
|
223
|
+
PATH="$blockbin:$basebin:$PATH" FM_BASE_TMUX="$basebin/tmux" FM_HOME="$home" \
|
|
224
|
+
FM_RECONCILE_RACE_ENTERED="$TMP_ROOT/reconcile-race.entered" \
|
|
225
|
+
FM_RECONCILE_RACE_RELEASE="$TMP_ROOT/reconcile-race.release" \
|
|
226
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
227
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/reconcile-race-tmux.log" \
|
|
228
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/reconcile-race-fake/pane.txt" \
|
|
229
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design reconcile-race \
|
|
230
|
+
> "$TMP_ROOT/reconcile-race.out" 2>&1 &
|
|
231
|
+
handoff=$!
|
|
232
|
+
i=0
|
|
233
|
+
while [ ! -f "$TMP_ROOT/reconcile-race.entered" ]; do
|
|
234
|
+
kill -0 "$handoff" 2>/dev/null || fail "reconciliation-race handoff exited before backend delivery"
|
|
235
|
+
i=$((i + 1))
|
|
236
|
+
[ "$i" -le 250 ] || fail "reconciliation-race handoff never reached backend delivery"
|
|
237
|
+
sleep 0.02
|
|
238
|
+
done
|
|
239
|
+
corr=$(cut -d: -f2- "$home/state/.backlog-handoff-design.wake-pending")
|
|
240
|
+
FM_PENDING_REPLY_NOW=9999999999 bash -c '
|
|
241
|
+
. "$1"
|
|
242
|
+
fm_pending_reply_reconcile_delivery "$2" "$3"
|
|
243
|
+
' _ "$ROOT/bin/fm-pending-reply-lib.sh" "$home/state" "$corr" \
|
|
244
|
+
|| fail "concurrent watcher could not reconcile the durable enqueue"
|
|
245
|
+
phase=$(sed -n 's/^phase=//p' "$home/state/pending-replies/$corr")
|
|
246
|
+
[ "$phase" = awaiting_report ] \
|
|
247
|
+
|| fail "advisory ring race changed the delivered expectation to $phase"
|
|
248
|
+
touch "$TMP_ROOT/reconcile-race.release"
|
|
249
|
+
wait "$handoff" || fail "failed advisory ring negated the durable enqueue"
|
|
250
|
+
assert_absent "$home/state/pending-replies/.delivery-confirmed-$corr" \
|
|
251
|
+
"delivered enqueue retained a recovery marker"
|
|
252
|
+
[ "$(inbox_record_count "$home/state" design)" -eq 1 ] \
|
|
253
|
+
|| fail "advisory ring race did not retain exactly one inbox record"
|
|
254
|
+
|
|
255
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design reconcile-race \
|
|
256
|
+
> "$TMP_ROOT/reconcile-race-retry.out" 2>&1 \
|
|
257
|
+
|| fail "idempotent handoff after the ring race failed: $(cat "$TMP_ROOT/reconcile-race-retry.out")"
|
|
258
|
+
[ "$(inbox_record_count "$home/state" design)" -eq 1 ] \
|
|
259
|
+
|| fail "ring-race retry duplicated the durable receiver instruction"
|
|
260
|
+
pass "concurrent reconciliation treats enqueue as delivery despite a failed advisory ring"
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
test_move_crash_keeps_wake_pending_for_recovery() {
|
|
264
|
+
local home="$TMP_ROOT/move-crash-main" sub="$TMP_ROOT/move-crash-sub"
|
|
265
|
+
local fakebin="$TMP_ROOT/move-crash-fakebin" real_tasks rc=0 prepared_state
|
|
266
|
+
setup_homes "$home" "$sub"
|
|
267
|
+
mkdir -p "$sub/data" "$fakebin"
|
|
268
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
269
|
+
## Queued
|
|
270
|
+
- [ ] crash-item - survive the post-move crash (repo: alpha)
|
|
271
|
+
|
|
272
|
+
## Done
|
|
273
|
+
EOF
|
|
274
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
275
|
+
real_tasks=$(command -v tasks-axi)
|
|
276
|
+
cat > "$fakebin/tasks-axi" <<'SH'
|
|
277
|
+
#!/usr/bin/env bash
|
|
278
|
+
"$FM_REAL_TASKS_AXI" "$@"
|
|
279
|
+
rc=$?
|
|
280
|
+
case " $* " in
|
|
281
|
+
*" --file "*" --to "*)
|
|
282
|
+
if [ "$rc" -eq 0 ] && [ "${1:-}" = mv ]; then
|
|
283
|
+
handoff_pid=$(ps -o ppid= -p "$PPID" | tr -d '[:space:]')
|
|
284
|
+
kill -KILL "$handoff_pid"
|
|
285
|
+
sleep 1
|
|
286
|
+
fi
|
|
287
|
+
;;
|
|
288
|
+
esac
|
|
289
|
+
exit "$rc"
|
|
290
|
+
SH
|
|
291
|
+
chmod +x "$fakebin/tasks-axi"
|
|
292
|
+
|
|
293
|
+
set +e
|
|
294
|
+
FM_REAL_TASKS_AXI="$real_tasks" PATH="$fakebin:$PATH" FM_HOME="$home" \
|
|
295
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design crash-item > "$TMP_ROOT/move-crash.out" 2>&1
|
|
296
|
+
rc=$?
|
|
297
|
+
set +e
|
|
298
|
+
[ "$rc" -ne 0 ] || fail "post-move crash fixture unexpectedly reported success"
|
|
299
|
+
assert_grep 'crash-item' "$sub/data/backlog.md" "post-move crash did not leave the item durable"
|
|
300
|
+
assert_present "$home/state/.backlog-handoff-design.wake-pending" \
|
|
301
|
+
"post-move crash lost receiver wake intent"
|
|
302
|
+
prepared_state=$(cat "$home/state/.backlog-handoff-design.wake-pending")
|
|
303
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
304
|
+
## Queued
|
|
305
|
+
- [ ] unrelated-move - still waiting in the main backlog (repo: alpha)
|
|
306
|
+
|
|
307
|
+
## Done
|
|
308
|
+
EOF
|
|
309
|
+
rc=0
|
|
310
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design unrelated-move \
|
|
311
|
+
> "$TMP_ROOT/move-crash-unrelated.out" 2>&1 || rc=$?
|
|
312
|
+
[ "$rc" -ne 0 ] || fail "unrelated moving handoff discarded a post-move prepared wake"
|
|
313
|
+
assert_contains "$(cat "$TMP_ROOT/move-crash-unrelated.out")" \
|
|
314
|
+
'belongs to a different routed batch' \
|
|
315
|
+
"unrelated handoff did not surface the unresolved prepared batch"
|
|
316
|
+
[ "$(cat "$home/state/.backlog-handoff-design.wake-pending")" = "$prepared_state" ] \
|
|
317
|
+
|| fail "unrelated moving handoff changed the post-move prepared wake"
|
|
318
|
+
assert_grep 'unrelated-move' "$home/data/backlog.md" \
|
|
319
|
+
"unrelated moving handoff changed its source item before resolving the older wake"
|
|
320
|
+
assert_no_grep 'unrelated-move' "$sub/data/backlog.md" \
|
|
321
|
+
"unrelated moving handoff moved work despite the unresolved older wake"
|
|
322
|
+
|
|
323
|
+
: > "$TMP_ROOT/default-tmux.log"
|
|
324
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design crash-item \
|
|
325
|
+
> "$TMP_ROOT/move-crash-retry.out" 2>&1 \
|
|
326
|
+
|| fail "post-move crash recovery failed: $(cat "$TMP_ROOT/move-crash-retry.out")"
|
|
327
|
+
assert_contains "$(inbox_body_stream "$home/state" design)" \
|
|
328
|
+
'New routed work is in your backlog.' \
|
|
329
|
+
"post-move crash recovery did not enqueue the receiver instruction"
|
|
330
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 1 ] \
|
|
331
|
+
|| fail "post-move crash recovery did not ring the receiver doorbell"
|
|
332
|
+
assert_absent "$home/state/.backlog-handoff-design.wake-pending" \
|
|
333
|
+
"confirmed crash recovery left receiver wake pending"
|
|
334
|
+
pass "a post-move crash preserves wake intent for an idempotent retry"
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
test_pre_move_crash_does_not_wake_until_move_lands() {
|
|
338
|
+
local home="$TMP_ROOT/pre-move-crash-main" sub="$TMP_ROOT/pre-move-crash-sub"
|
|
339
|
+
local fakebin="$TMP_ROOT/pre-move-crash-fakebin" real_tasks rc=0 wake_count
|
|
340
|
+
setup_homes "$home" "$sub"
|
|
341
|
+
mkdir -p "$sub/data" "$fakebin"
|
|
342
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
343
|
+
## Queued
|
|
344
|
+
- [ ] pre-move-crash - wake only after durable move (repo: alpha)
|
|
345
|
+
|
|
346
|
+
## Done
|
|
347
|
+
EOF
|
|
348
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
349
|
+
real_tasks=$(command -v tasks-axi)
|
|
350
|
+
cat > "$fakebin/tasks-axi" <<'SH'
|
|
351
|
+
#!/usr/bin/env bash
|
|
352
|
+
case " $* " in
|
|
353
|
+
*" --file "*" --to "*)
|
|
354
|
+
if [ "${1:-}" = mv ]; then
|
|
355
|
+
handoff_pid=$(ps -o ppid= -p "$PPID" | tr -d '[:space:]')
|
|
356
|
+
kill -KILL "$handoff_pid"
|
|
357
|
+
sleep 1
|
|
358
|
+
fi
|
|
359
|
+
;;
|
|
360
|
+
esac
|
|
361
|
+
exec "$FM_REAL_TASKS_AXI" "$@"
|
|
362
|
+
SH
|
|
363
|
+
chmod +x "$fakebin/tasks-axi"
|
|
364
|
+
: > "$TMP_ROOT/default-tmux.log"
|
|
365
|
+
|
|
366
|
+
set +e
|
|
367
|
+
FM_REAL_TASKS_AXI="$real_tasks" PATH="$fakebin:$PATH" FM_HOME="$home" \
|
|
368
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design pre-move-crash > "$TMP_ROOT/pre-move-crash.out" 2>&1
|
|
369
|
+
rc=$?
|
|
370
|
+
set -e
|
|
371
|
+
[ "$rc" -ne 0 ] || fail "pre-move crash fixture unexpectedly reported success"
|
|
372
|
+
assert_grep 'pre-move-crash' "$home/data/backlog.md" "pre-move crash changed the source backlog"
|
|
373
|
+
assert_no_grep 'pre-move-crash' "$sub/data/backlog.md" "pre-move crash changed the destination backlog"
|
|
374
|
+
assert_present "$home/state/.backlog-handoff-design.wake-pending" \
|
|
375
|
+
"pre-move crash lost its prepared wake intent"
|
|
376
|
+
|
|
377
|
+
cat > "$sub/data/backlog.md" <<'EOF'
|
|
378
|
+
## Queued
|
|
379
|
+
- [ ] unrelated-ready - already durable from another handoff (repo: alpha)
|
|
380
|
+
|
|
381
|
+
## Done
|
|
382
|
+
EOF
|
|
383
|
+
rc=0
|
|
384
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design unrelated-ready \
|
|
385
|
+
> "$TMP_ROOT/pre-move-unrelated.out" 2>&1 || rc=$?
|
|
386
|
+
[ "$rc" -ne 0 ] || fail "unrelated handoff accepted another batch's prepared wake"
|
|
387
|
+
assert_contains "$(cat "$TMP_ROOT/pre-move-unrelated.out")" \
|
|
388
|
+
'belongs to a different routed batch' \
|
|
389
|
+
"unrelated handoff did not report the prepared batch conflict"
|
|
390
|
+
[ ! -s "$TMP_ROOT/default-tmux.log" ] \
|
|
391
|
+
|| fail "unrelated already-present work promoted another batch's prepared wake"
|
|
392
|
+
assert_grep 'pre-move-crash' "$home/data/backlog.md" \
|
|
393
|
+
"unrelated handoff changed the prepared batch's source item"
|
|
394
|
+
assert_present "$home/state/.backlog-handoff-design.wake-pending" \
|
|
395
|
+
"unrelated handoff discarded another batch's prepared wake"
|
|
396
|
+
|
|
397
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design pre-move-crash \
|
|
398
|
+
> "$TMP_ROOT/pre-move-crash-retry.out" 2>&1 \
|
|
399
|
+
|| fail "pre-move crash recovery failed: $(cat "$TMP_ROOT/pre-move-crash-retry.out")"
|
|
400
|
+
assert_grep 'pre-move-crash' "$sub/data/backlog.md" "pre-move crash recovery did not move the item"
|
|
401
|
+
wake_count=$(inbox_record_count "$home/state" design)
|
|
402
|
+
[ "$wake_count" -eq 1 ] || fail "pre-move crash recovery emitted $wake_count receiver records"
|
|
403
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 1 ] \
|
|
404
|
+
|| fail "pre-move crash recovery did not ring exactly one receiver doorbell"
|
|
405
|
+
pass "a pre-move crash wakes only after retry makes the item durable"
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
test_delivery_confirmation_crash_does_not_resend() {
|
|
409
|
+
local home="$TMP_ROOT/confirm-crash-main" sub="$TMP_ROOT/confirm-crash-sub"
|
|
410
|
+
local fakebin="$TMP_ROOT/confirm-crash-fakebin" real_rm rc=0 wake_count
|
|
411
|
+
setup_homes "$home" "$sub"
|
|
412
|
+
mkdir -p "$sub/data" "$fakebin"
|
|
413
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
414
|
+
## Queued
|
|
415
|
+
- [ ] confirm-crash - preserve confirmed delivery (repo: alpha)
|
|
416
|
+
|
|
417
|
+
## Done
|
|
418
|
+
EOF
|
|
419
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
420
|
+
real_rm=$(command -v rm)
|
|
421
|
+
cat > "$fakebin/rm" <<'SH'
|
|
422
|
+
#!/usr/bin/env bash
|
|
423
|
+
for arg in "$@"; do
|
|
424
|
+
if [ "$arg" = "$FM_CONFIRM_WAKE_MARKER" ] \
|
|
425
|
+
&& mkdir "$FM_CONFIRM_CRASH_ONCE" 2>/dev/null; then
|
|
426
|
+
kill -KILL "$PPID"
|
|
427
|
+
exit 0
|
|
428
|
+
fi
|
|
429
|
+
done
|
|
430
|
+
exec "$FM_REAL_RM" "$@"
|
|
431
|
+
SH
|
|
432
|
+
chmod +x "$fakebin/rm"
|
|
433
|
+
: > "$TMP_ROOT/default-tmux.log"
|
|
434
|
+
|
|
435
|
+
set +e
|
|
436
|
+
PATH="$fakebin:$PATH" FM_REAL_RM="$real_rm" \
|
|
437
|
+
FM_CONFIRM_CRASH_ONCE="$TMP_ROOT/confirm-crash.once" \
|
|
438
|
+
FM_CONFIRM_WAKE_MARKER="$home/state/.backlog-handoff-design.wake-pending" \
|
|
439
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design confirm-crash \
|
|
440
|
+
> "$TMP_ROOT/confirm-crash.out" 2>&1
|
|
441
|
+
rc=$?
|
|
442
|
+
set +e
|
|
443
|
+
[ "$rc" -ne 0 ] || fail "post-confirmation crash fixture unexpectedly reported success"
|
|
444
|
+
wake_count=$(inbox_record_count "$home/state" design)
|
|
445
|
+
[ "$wake_count" -eq 1 ] || fail "post-confirmation crash did not deliver exactly one receiver record"
|
|
446
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 1 ] \
|
|
447
|
+
|| fail "post-confirmation crash did not ring exactly one receiver doorbell"
|
|
448
|
+
case "$(cat "$home/state/.backlog-handoff-design.wake-pending")" in
|
|
449
|
+
pending:*) ;;
|
|
450
|
+
*) fail "post-confirmation crash lost its stable delivery correlation" ;;
|
|
451
|
+
esac
|
|
452
|
+
|
|
453
|
+
# Route different work before explicitly retrying the crashed invocation. The
|
|
454
|
+
# completed old correlation must be reconciled, but must not stand in as the
|
|
455
|
+
# delivery proof for this new durable move.
|
|
456
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
457
|
+
## Queued
|
|
458
|
+
- [ ] after-crash - requires its own receiver wake (repo: alpha)
|
|
459
|
+
|
|
460
|
+
## Done
|
|
461
|
+
EOF
|
|
462
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design after-crash \
|
|
463
|
+
> "$TMP_ROOT/after-confirm-crash.out" 2>&1 \
|
|
464
|
+
|| fail "new handoff after a confirmation crash failed: $(cat "$TMP_ROOT/after-confirm-crash.out")"
|
|
465
|
+
[ "$(inbox_record_count "$home/state" design)" -eq "$((wake_count + 1))" ] \
|
|
466
|
+
|| fail "completed stale correlation suppressed or duplicated the new receiver record"
|
|
467
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 2 ] \
|
|
468
|
+
|| fail "completed stale correlation suppressed or duplicated the new doorbell"
|
|
469
|
+
assert_grep 'after-crash' "$sub/data/backlog.md" \
|
|
470
|
+
"new item after a confirmation crash was not durably handed off"
|
|
471
|
+
|
|
472
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design confirm-crash \
|
|
473
|
+
> "$TMP_ROOT/confirm-crash-retry.out" 2>&1 \
|
|
474
|
+
|| fail "post-confirmation crash recovery failed: $(cat "$TMP_ROOT/confirm-crash-retry.out")"
|
|
475
|
+
[ "$(inbox_record_count "$home/state" design)" -eq "$((wake_count + 1))" ] \
|
|
476
|
+
|| fail "post-confirmation crash recovery duplicated the receiver record"
|
|
477
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 2 ] \
|
|
478
|
+
|| fail "post-confirmation crash recovery duplicated the doorbell"
|
|
479
|
+
assert_absent "$home/state/.backlog-handoff-design.wake-pending" \
|
|
480
|
+
"post-confirmation crash recovery left wake state pending"
|
|
481
|
+
pass "a post-confirmation crash reconciles once without suppressing a later handoff wake"
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
test_unresolved_delivery_attempt_refuses_immediate_resend() {
|
|
485
|
+
local home="$TMP_ROOT/attempt-crash-main" sub="$TMP_ROOT/attempt-crash-sub"
|
|
486
|
+
local fakebin="$TMP_ROOT/attempt-crash-fakebin" real_mv rc=0 wake_count out
|
|
487
|
+
setup_homes "$home" "$sub"
|
|
488
|
+
mkdir -p "$sub/data" "$fakebin"
|
|
489
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
490
|
+
## Queued
|
|
491
|
+
- [ ] attempt-crash - do not resend an unresolved delivery (repo: alpha)
|
|
492
|
+
|
|
493
|
+
## Done
|
|
494
|
+
EOF
|
|
495
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
496
|
+
real_mv=$(command -v mv)
|
|
497
|
+
cat > "$fakebin/mv" <<'SH'
|
|
498
|
+
#!/usr/bin/env bash
|
|
499
|
+
for arg in "$@"; do
|
|
500
|
+
if [ -f "$arg" ] && grep -q '^confirmed=' "$arg" 2>/dev/null; then
|
|
501
|
+
kill -KILL "$PPID"
|
|
502
|
+
exit 1
|
|
503
|
+
fi
|
|
504
|
+
done
|
|
505
|
+
exec "$FM_REAL_MV" "$@"
|
|
506
|
+
SH
|
|
507
|
+
chmod +x "$fakebin/mv"
|
|
508
|
+
: > "$TMP_ROOT/default-tmux.log"
|
|
509
|
+
|
|
510
|
+
set +e
|
|
511
|
+
PATH="$fakebin:$PATH" FM_REAL_MV="$real_mv" FM_HOME="$home" \
|
|
512
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design attempt-crash \
|
|
513
|
+
> "$TMP_ROOT/attempt-crash.out" 2>&1
|
|
514
|
+
rc=$?
|
|
515
|
+
set +e
|
|
516
|
+
[ "$rc" -ne 0 ] || fail "unresolved-attempt crash fixture unexpectedly reported success"
|
|
517
|
+
wake_count=$(inbox_record_count "$home/state" design)
|
|
518
|
+
[ "$wake_count" -eq 1 ] || fail "unresolved-attempt crash did not deliver exactly one receiver record"
|
|
519
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 0 ] \
|
|
520
|
+
|| fail "delivery bookkeeping crash unexpectedly reached the later doorbell step"
|
|
521
|
+
|
|
522
|
+
rc=0
|
|
523
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design attempt-crash 2>&1) || rc=$?
|
|
524
|
+
[ "$rc" -ne 0 ] || fail "immediate retry resent or accepted an unresolved delivery attempt"
|
|
525
|
+
assert_contains "$out" 'delivery for design is unresolved; refusing to resend correlation' \
|
|
526
|
+
"immediate retry did not report the unresolved delivery boundary"
|
|
527
|
+
[ "$(inbox_record_count "$home/state" design)" -eq "$wake_count" ] \
|
|
528
|
+
|| fail "immediate retry duplicated the unresolved receiver record"
|
|
529
|
+
[ "$(doorbell_count "$TMP_ROOT/default-tmux.log")" -eq 0 ] \
|
|
530
|
+
|| fail "immediate retry rang for an unresolved delivery correlation"
|
|
531
|
+
pass "an unresolved delivery attempt refuses an immediate duplicate wake"
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
test_concurrent_local_handoffs_serialize_move_and_wake() {
|
|
535
|
+
local home="$TMP_ROOT/concurrent-main" sub="$TMP_ROOT/concurrent-sub"
|
|
536
|
+
local basebin blockbin="$TMP_ROOT/concurrent-blockbin" first second i wake_count
|
|
537
|
+
setup_homes "$home" "$sub"
|
|
538
|
+
mkdir -p "$sub/data" "$blockbin"
|
|
539
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
540
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
541
|
+
## Queued
|
|
542
|
+
- [ ] concurrent-a - first routed item (repo: alpha)
|
|
543
|
+
|
|
544
|
+
## Done
|
|
545
|
+
EOF
|
|
546
|
+
basebin=$(make_fake_tmux "$TMP_ROOT/concurrent-fake")
|
|
547
|
+
cat > "$blockbin/tmux" <<'SH'
|
|
548
|
+
#!/usr/bin/env bash
|
|
549
|
+
case "$*" in
|
|
550
|
+
*"Firstmate instruction waiting:"*)
|
|
551
|
+
if mkdir "$FM_BLOCK_WAKE_ONCE" 2>/dev/null; then
|
|
552
|
+
touch "$FM_BLOCK_WAKE_ENTERED"
|
|
553
|
+
while [ ! -f "$FM_BLOCK_WAKE_RELEASE" ]; do sleep 0.02; done
|
|
554
|
+
fi
|
|
555
|
+
;;
|
|
556
|
+
esac
|
|
557
|
+
exec "$FM_BASE_TMUX" "$@"
|
|
558
|
+
SH
|
|
559
|
+
chmod +x "$blockbin/tmux"
|
|
560
|
+
|
|
561
|
+
PATH="$blockbin:$basebin:$PATH" FM_HOME="$home" FM_BASE_TMUX="$basebin/tmux" \
|
|
562
|
+
FM_BLOCK_WAKE_ONCE="$TMP_ROOT/concurrent.once" \
|
|
563
|
+
FM_BLOCK_WAKE_ENTERED="$TMP_ROOT/concurrent.entered" \
|
|
564
|
+
FM_BLOCK_WAKE_RELEASE="$TMP_ROOT/concurrent.release" \
|
|
565
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
566
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/concurrent-tmux.log" \
|
|
567
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/concurrent-fake/pane.txt" \
|
|
568
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design concurrent-a > "$TMP_ROOT/concurrent-a.out" 2>&1 &
|
|
569
|
+
first=$!
|
|
570
|
+
i=0
|
|
571
|
+
while [ ! -f "$TMP_ROOT/concurrent.entered" ]; do
|
|
572
|
+
kill -0 "$first" 2>/dev/null || fail "first concurrent handoff exited before its blocked wake"
|
|
573
|
+
i=$((i + 1))
|
|
574
|
+
[ "$i" -le 250 ] || fail "first concurrent handoff never reached its receiver wake"
|
|
575
|
+
sleep 0.02
|
|
576
|
+
done
|
|
577
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
578
|
+
## Queued
|
|
579
|
+
- [ ] concurrent-b - second routed item (repo: alpha)
|
|
580
|
+
|
|
581
|
+
## Done
|
|
582
|
+
EOF
|
|
583
|
+
PATH="$blockbin:$basebin:$PATH" FM_HOME="$home" FM_BASE_TMUX="$basebin/tmux" \
|
|
584
|
+
FM_BLOCK_WAKE_ONCE="$TMP_ROOT/concurrent.once" \
|
|
585
|
+
FM_BLOCK_WAKE_ENTERED="$TMP_ROOT/concurrent.entered" \
|
|
586
|
+
FM_BLOCK_WAKE_RELEASE="$TMP_ROOT/concurrent.release" \
|
|
587
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
588
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/concurrent-tmux.log" \
|
|
589
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/concurrent-fake/pane.txt" \
|
|
590
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design concurrent-b > "$TMP_ROOT/concurrent-b.out" 2>&1 &
|
|
591
|
+
second=$!
|
|
592
|
+
sleep 0.2
|
|
593
|
+
assert_grep 'concurrent-b' "$home/data/backlog.md" \
|
|
594
|
+
"second local handoff moved while the first still owned its wake"
|
|
595
|
+
touch "$TMP_ROOT/concurrent.release"
|
|
596
|
+
wait "$first" || fail "first serialized local handoff failed"
|
|
597
|
+
wait "$second" || fail "second serialized local handoff failed"
|
|
598
|
+
assert_grep 'concurrent-a' "$sub/data/backlog.md" "first serialized item was lost"
|
|
599
|
+
assert_grep 'concurrent-b' "$sub/data/backlog.md" "second serialized item was lost"
|
|
600
|
+
wake_count=$(inbox_record_count "$home/state" design)
|
|
601
|
+
[ "$wake_count" -eq 2 ] || fail "serialized local handoffs produced $wake_count receiver records"
|
|
602
|
+
[ "$(doorbell_count "$TMP_ROOT/concurrent-tmux.log")" -eq 2 ] \
|
|
603
|
+
|| fail "serialized local handoffs did not produce two receiver doorbells"
|
|
604
|
+
pass "concurrent local handoffs serialize each durable move with its wake"
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
test_local_teardown_waits_for_handoff_wake() {
|
|
608
|
+
local home="$TMP_ROOT/teardown-race-main" sub="$TMP_ROOT/teardown-race-sub"
|
|
609
|
+
local basebin blockbin="$TMP_ROOT/teardown-race-blockbin" handoff teardown i
|
|
610
|
+
setup_homes "$home" "$sub"
|
|
611
|
+
printf 'project=%s\n' "$ROOT" >> "$home/state/design.meta"
|
|
612
|
+
mkdir -p "$sub/data" "$blockbin"
|
|
613
|
+
printf '## Queued\n\n## Done\n' > "$sub/data/backlog.md"
|
|
614
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
615
|
+
## Queued
|
|
616
|
+
- [ ] teardown-race - routed while teardown starts (repo: alpha)
|
|
617
|
+
|
|
618
|
+
## Done
|
|
619
|
+
EOF
|
|
620
|
+
basebin=$(make_fake_tmux "$TMP_ROOT/teardown-race-fake")
|
|
621
|
+
cat > "$blockbin/tmux" <<'SH'
|
|
622
|
+
#!/usr/bin/env bash
|
|
623
|
+
case "$*" in
|
|
624
|
+
*"Firstmate instruction waiting:"*)
|
|
625
|
+
touch "$FM_BLOCK_WAKE_ENTERED"
|
|
626
|
+
while [ ! -f "$FM_BLOCK_WAKE_RELEASE" ]; do sleep 0.02; done
|
|
627
|
+
;;
|
|
628
|
+
esac
|
|
629
|
+
exec "$FM_BASE_TMUX" "$@"
|
|
630
|
+
SH
|
|
631
|
+
chmod +x "$blockbin/tmux"
|
|
632
|
+
PATH="$blockbin:$basebin:$PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
633
|
+
FM_BASE_TMUX="$basebin/tmux" FM_BLOCK_WAKE_ENTERED="$TMP_ROOT/teardown-race.entered" \
|
|
634
|
+
FM_BLOCK_WAKE_RELEASE="$TMP_ROOT/teardown-race.release" \
|
|
635
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
636
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/teardown-race-tmux.log" \
|
|
637
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/teardown-race-fake/pane.txt" \
|
|
638
|
+
"$ROOT/bin/fm-backlog-handoff.sh" design teardown-race > "$TMP_ROOT/teardown-race-handoff.out" 2>&1 &
|
|
639
|
+
handoff=$!
|
|
640
|
+
i=0
|
|
641
|
+
while [ ! -f "$TMP_ROOT/teardown-race.entered" ]; do
|
|
642
|
+
kill -0 "$handoff" 2>/dev/null || fail "teardown-race handoff exited before its blocked wake"
|
|
643
|
+
i=$((i + 1))
|
|
644
|
+
[ "$i" -le 250 ] || fail "teardown-race handoff never reached its receiver wake"
|
|
645
|
+
sleep 0.02
|
|
646
|
+
done
|
|
647
|
+
PATH="$basebin:$PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
648
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
649
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/teardown-race-tmux.log" \
|
|
650
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/teardown-race-fake/pane.txt" \
|
|
651
|
+
"$ROOT/bin/fm-teardown.sh" design --force > "$TMP_ROOT/teardown-race-teardown.out" 2>&1 &
|
|
652
|
+
teardown=$!
|
|
653
|
+
sleep 0.3
|
|
654
|
+
kill -0 "$teardown" 2>/dev/null \
|
|
655
|
+
|| fail "local teardown bypassed the in-flight handoff lock: $(cat "$TMP_ROOT/teardown-race-teardown.out")"
|
|
656
|
+
[ -d "$sub" ] || fail "local teardown removed the receiver home before handoff wake completed"
|
|
657
|
+
assert_grep 'teardown-race' "$sub/data/backlog.md" \
|
|
658
|
+
"local teardown removed routed work before handoff wake completed"
|
|
659
|
+
touch "$TMP_ROOT/teardown-race.release"
|
|
660
|
+
wait "$handoff" || fail "teardown-race handoff failed after releasing its wake"
|
|
661
|
+
wait "$teardown" 2>/dev/null || true
|
|
662
|
+
pass "local teardown waits for the routed move and receiver wake"
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
test_local_teardown_preserves_wake_when_home_removal_fails() {
|
|
666
|
+
local home="$TMP_ROOT/teardown-home-fail-main" sub="$TMP_ROOT/teardown-home-fail-sub"
|
|
667
|
+
local fakebin rm_bin="$TMP_ROOT/teardown-home-fail-rm" real_rm corr rc=0 marker rec fail_home
|
|
668
|
+
local marker_before="$TMP_ROOT/teardown-home-fail-marker.before"
|
|
669
|
+
local rec_before="$TMP_ROOT/teardown-home-fail-record.before"
|
|
670
|
+
setup_homes "$home" "$sub"
|
|
671
|
+
printf 'project=%s\n' "$ROOT" >> "$home/state/design.meta"
|
|
672
|
+
mkdir -p "$sub/data" "$rm_bin"
|
|
673
|
+
printf '## Queued\n- [ ] still-routed - preserve its wake (repo: alpha)\n\n## Done\n' > "$sub/data/backlog.md"
|
|
674
|
+
corr=$(FM_HOME="$home" bash -c '
|
|
675
|
+
. "$1"
|
|
676
|
+
fm_pending_reply_create "$2" "$2/state" design "New routed work is in your backlog."
|
|
677
|
+
' _ "$ROOT/bin/fm-pending-reply-lib.sh" "$home") \
|
|
678
|
+
|| fail "could not seed teardown wake state"
|
|
679
|
+
marker="$home/state/.backlog-handoff-design.wake-pending"
|
|
680
|
+
rec="$home/state/pending-replies/$corr"
|
|
681
|
+
printf 'pending:%s\n' "$corr" > "$marker"
|
|
682
|
+
cp -p -- "$marker" "$marker_before"
|
|
683
|
+
cp -p -- "$rec" "$rec_before"
|
|
684
|
+
real_rm=$(command -v rm)
|
|
685
|
+
fail_home=$(cd "$sub" && pwd -P)
|
|
686
|
+
cat > "$rm_bin/rm" <<'SH'
|
|
687
|
+
#!/usr/bin/env bash
|
|
688
|
+
for arg in "$@"; do
|
|
689
|
+
[ "$arg" != "$FM_FAIL_HOME" ] || exit 1
|
|
690
|
+
done
|
|
691
|
+
exec "$FM_REAL_RM" "$@"
|
|
692
|
+
SH
|
|
693
|
+
chmod +x "$rm_bin/rm"
|
|
694
|
+
fakebin=$(make_fake_tmux "$TMP_ROOT/teardown-home-fail-fake")
|
|
695
|
+
|
|
696
|
+
set +e
|
|
697
|
+
PATH="$rm_bin:$fakebin:$PATH" FM_REAL_RM="$real_rm" FM_FAIL_HOME="$fail_home" \
|
|
698
|
+
FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
699
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
700
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/teardown-home-fail-tmux.log" \
|
|
701
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/teardown-home-fail-fake/pane.txt" \
|
|
702
|
+
"$ROOT/bin/fm-teardown.sh" design --force > "$TMP_ROOT/teardown-home-fail.out" 2>&1
|
|
703
|
+
rc=$?
|
|
704
|
+
set -e
|
|
705
|
+
[ "$rc" -ne 0 ] || fail "teardown ignored the receiver-home removal failure"
|
|
706
|
+
assert_present "$sub" "failed teardown did not preserve the receiver home"
|
|
707
|
+
assert_grep 'still-routed' "$sub/data/backlog.md" "failed teardown lost routed backlog work"
|
|
708
|
+
cmp -s "$marker_before" "$marker" \
|
|
709
|
+
|| fail "failed home removal changed the pending wake marker"
|
|
710
|
+
cmp -s "$rec_before" "$rec" \
|
|
711
|
+
|| fail "failed home removal changed the pending wake correlation"
|
|
712
|
+
assert_present "$home/state/design.meta" "failed teardown removed route metadata"
|
|
713
|
+
assert_grep '- design ' "$home/data/secondmates.md" "failed teardown removed the registry route"
|
|
714
|
+
|
|
715
|
+
PATH="$fakebin:$PATH" FM_HOME="$home" FM_ROOT_OVERRIDE="$ROOT" \
|
|
716
|
+
FM_FAKE_TMUX_WINDOW='firstmate:fm-design' \
|
|
717
|
+
FM_FAKE_TMUX_LOG="$TMP_ROOT/teardown-home-fail-tmux.log" \
|
|
718
|
+
FM_FAKE_TMUX_CAPTURE="$TMP_ROOT/teardown-home-fail-fake/pane.txt" \
|
|
719
|
+
"$ROOT/bin/fm-teardown.sh" design --force > "$TMP_ROOT/teardown-home-retry.out" 2>&1 \
|
|
720
|
+
|| fail "teardown retry did not retire the preserved wake: $(cat "$TMP_ROOT/teardown-home-retry.out")"
|
|
721
|
+
assert_absent "$sub" "teardown retry left the receiver home"
|
|
722
|
+
assert_absent "$marker" "teardown retry left the pending wake marker"
|
|
723
|
+
assert_absent "$rec" "teardown retry left the pending wake correlation"
|
|
724
|
+
assert_no_grep '- design ' "$home/data/secondmates.md" "teardown retry left the registry route"
|
|
725
|
+
pass "failed local home removal preserves its wake and a retry retires both"
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
# Exact multi-line block extract: header matching key plus following body lines
|
|
729
|
+
# (indented lines and blank separators between paragraphs), stopping at the next
|
|
730
|
+
# item header or unindented section heading (column-0 ##).
|
|
731
|
+
extract_item_block() {
|
|
732
|
+
local file=$1 key=$2
|
|
733
|
+
awk -v key="$key" '
|
|
734
|
+
/^- \[[ x]\] / {
|
|
735
|
+
rest = $0
|
|
736
|
+
sub(/^- \[[ x]\] +/, "", rest)
|
|
737
|
+
id = rest
|
|
738
|
+
sub(/[ \t].*/, "", id)
|
|
739
|
+
if (capturing) exit
|
|
740
|
+
if (id == key) { print; capturing = 1; next }
|
|
741
|
+
next
|
|
742
|
+
}
|
|
743
|
+
capturing && /^## / { exit }
|
|
744
|
+
capturing && /^- \[[ x]\] / { exit }
|
|
745
|
+
capturing && /^([ \t].*)?$/ { print; next }
|
|
746
|
+
capturing { exit }
|
|
747
|
+
' "$file"
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
assert_block_equals() {
|
|
751
|
+
local label=$1 expected=$2 actual=$3
|
|
752
|
+
if [ "$expected" != "$actual" ]; then
|
|
753
|
+
printf 'expected block:\n%s\nactual block:\n%s\n' "$expected" "$actual" >&2
|
|
754
|
+
fail "$label"
|
|
755
|
+
fi
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
# seed_public_commitment <home> <obligation> <work-home> <work-id>: the intake
|
|
759
|
+
# half of a promised public reply - the typed obligation, its bound work, and
|
|
760
|
+
# this home's registration - so a later handoff can be observed against a real
|
|
761
|
+
# unresolved commitment rather than a stub.
|
|
762
|
+
seed_public_commitment() {
|
|
763
|
+
local home=$1 obligation=$2 work_home=$3 work_id=$4
|
|
764
|
+
printf 'FMX_PAIRING_TOKEN=test-token\n' > "$home/.env"
|
|
765
|
+
cp "$ROOT/.tasks.toml" "$home/.tasks.toml"
|
|
766
|
+
jq -n '{request_id:"req-handoff", platform:"x",
|
|
767
|
+
context_binding:{version:"ctx1", value:"ctx1_req-handoff"},
|
|
768
|
+
public_safe_summary:"looking into the sign-in redirect",
|
|
769
|
+
received_at:"2026-07-30T10:00:00Z",
|
|
770
|
+
followup_expires_at:"2026-08-06T10:00:00Z",
|
|
771
|
+
reservation_expires_at:"2026-08-06T10:00:00Z"}' > "$home/request.json"
|
|
772
|
+
jq -n '{type:"pr-merged", project:"alpha",
|
|
773
|
+
required_deliverables:["pr_url"], completion_policy:"all-required"}' \
|
|
774
|
+
> "$home/expected.json"
|
|
775
|
+
jq -n --arg h "$work_home" --arg w "$work_id" \
|
|
776
|
+
'{relation_id:"rel-code", work_ref:{home_id:$h, task_id:$w},
|
|
777
|
+
role:"fulfills", required:true, generation:1}' > "$home/relation.json"
|
|
778
|
+
(cd "$home" && tasks-axi public-followup add "$obligation" \
|
|
779
|
+
--request-context-file "$home/request.json" --purpose promised-final \
|
|
780
|
+
--expected-final-file "$home/expected.json" --expires-at 2026-10-01T00:00:00Z) >/dev/null \
|
|
781
|
+
|| fail "could not create the public commitment"
|
|
782
|
+
(cd "$home" && tasks-axi public-followup bind-work "$obligation" \
|
|
783
|
+
--relation-file "$home/relation.json") >/dev/null \
|
|
784
|
+
|| fail "could not bind work to the public commitment"
|
|
785
|
+
FM_ROOT_OVERRIDE="$ROOT" FM_HOME="$home" "$ROOT/bin/fm-public-followup.sh" register \
|
|
786
|
+
"$obligation" --relation rel-code --work-home "$work_home" --work-id "$work_id" \
|
|
787
|
+
--generation 1 >/dev/null \
|
|
788
|
+
|| fail "could not register the public commitment"
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
# A public promise binds its work by home AND id. Handing that work to a
|
|
792
|
+
# secondmate leaves the binding naming a home that no longer owns it, which used
|
|
793
|
+
# to go unnoticed until the promised reply was never delivered. The move itself
|
|
794
|
+
# stays safe; the staleness must be reported at the moment it is created.
|
|
795
|
+
test_handoff_warns_when_a_moved_item_still_owes_a_public_reply() {
|
|
796
|
+
local home="$TMP_ROOT/pf-stale-main"
|
|
797
|
+
local sub="$TMP_ROOT/pf-stale-sub"
|
|
798
|
+
command -v jq >/dev/null 2>&1 || { echo "skip: jq not found (required by the public-commitment guard)"; return 0; }
|
|
799
|
+
setup_homes "$home" "$sub"
|
|
800
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
801
|
+
## Queued
|
|
802
|
+
- [ ] promised-item - fix the sign-in redirect (repo: alpha)
|
|
803
|
+
- [ ] plain-item - unrelated queued work (repo: alpha)
|
|
804
|
+
|
|
805
|
+
## Done
|
|
806
|
+
EOF
|
|
807
|
+
seed_public_commitment "$home" pf-handoff main promised-item
|
|
808
|
+
|
|
809
|
+
local out rc=0
|
|
810
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design promised-item plain-item 2>&1) || rc=$?
|
|
811
|
+
[ "$rc" -eq 0 ] || fail "handoff must still succeed while reporting the stale binding: $out"
|
|
812
|
+
assert_contains "$out" "handed off 2 item(s)" "the move itself must still be reported"
|
|
813
|
+
assert_grep 'promised-item' "$sub/data/backlog.md" "the promised item did not reach the secondmate backlog"
|
|
814
|
+
assert_contains "$out" "promised-item still owes a public reply bound to main/promised-item" \
|
|
815
|
+
"the stale public-commitment binding was not reported"
|
|
816
|
+
# The report must come from a genuinely unresolved commitment, not from a state
|
|
817
|
+
# the guard merely could not verify.
|
|
818
|
+
assert_contains "$out" "public commitment pf-handoff is still" \
|
|
819
|
+
"the report did not carry the unresolved commitment the guard actually found"
|
|
820
|
+
assert_contains "$out" "--work-home secondmate:design" \
|
|
821
|
+
"the report did not name the rebinding that keeps the promise reachable"
|
|
822
|
+
case "$out" in
|
|
823
|
+
*"plain-item still owes"*) fail "an item with no public commitment must not be reported" ;;
|
|
824
|
+
esac
|
|
825
|
+
|
|
826
|
+
pass "handoff reports a moved item whose public commitment still binds this home"
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
# A home that never opted into the relay must pay nothing and say nothing here.
|
|
830
|
+
test_handoff_is_silent_about_public_commitments_without_the_relay() {
|
|
831
|
+
local home="$TMP_ROOT/pf-silent-main"
|
|
832
|
+
local sub="$TMP_ROOT/pf-silent-sub"
|
|
833
|
+
setup_homes "$home" "$sub"
|
|
834
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
835
|
+
## Queued
|
|
836
|
+
- [ ] quiet-item - ordinary queued work (repo: alpha)
|
|
837
|
+
|
|
838
|
+
## Done
|
|
839
|
+
EOF
|
|
840
|
+
|
|
841
|
+
local out rc=0
|
|
842
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design quiet-item 2>&1) || rc=$?
|
|
843
|
+
[ "$rc" -eq 0 ] || fail "handoff failed in a relay-free home: $out"
|
|
844
|
+
case "$out" in
|
|
845
|
+
*"public reply"*) fail "a relay-free home must not mention public commitments: $out" ;;
|
|
846
|
+
esac
|
|
847
|
+
assert_grep 'quiet-item' "$sub/data/backlog.md" "the item did not reach the secondmate backlog"
|
|
848
|
+
pass "handoff says nothing about public commitments in a relay-free home"
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
test_body_moves_when_followed_by_another_item() {
|
|
852
|
+
local home="$TMP_ROOT/body-next-item-main"
|
|
853
|
+
local sub="$TMP_ROOT/body-next-item-sub"
|
|
854
|
+
setup_homes "$home" "$sub"
|
|
855
|
+
|
|
856
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
857
|
+
## Queued
|
|
858
|
+
- [ ] keep-a - stays first (repo: alpha)
|
|
859
|
+
keep-a body line
|
|
860
|
+
- [ ] body-item - has a body (repo: alpha)
|
|
861
|
+
Spec detail one.
|
|
862
|
+
## Intent
|
|
863
|
+
Move the full block.
|
|
864
|
+
trailing body line
|
|
865
|
+
- [ ] keep-b - stays after (repo: beta)
|
|
866
|
+
keep-b body stays
|
|
867
|
+
|
|
868
|
+
## Done
|
|
869
|
+
EOF
|
|
870
|
+
|
|
871
|
+
local expected_block
|
|
872
|
+
expected_block=$(extract_item_block "$home/data/backlog.md" body-item)
|
|
873
|
+
|
|
874
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design body-item >/dev/null \
|
|
875
|
+
|| fail "handoff of body-followed-by-item failed"
|
|
876
|
+
|
|
877
|
+
local dest_block
|
|
878
|
+
dest_block=$(extract_item_block "$sub/data/backlog.md" body-item)
|
|
879
|
+
assert_block_equals "destination body block mismatch after item-boundary handoff" \
|
|
880
|
+
"$expected_block" "$dest_block"
|
|
881
|
+
|
|
882
|
+
assert_no_grep 'body-item' "$home/data/backlog.md" "body-item header still in source"
|
|
883
|
+
assert_no_grep 'Spec detail one' "$home/data/backlog.md" "orphaned body line stayed in source"
|
|
884
|
+
assert_no_grep 'Move the full block' "$home/data/backlog.md" "orphaned body line stayed in source"
|
|
885
|
+
assert_no_grep 'trailing body line' "$home/data/backlog.md" "orphaned trailing body stayed in source"
|
|
886
|
+
# Indented heading must move with the item, not be left or treated as a section.
|
|
887
|
+
assert_no_grep '## Intent' "$home/data/backlog.md" "indented ## Intent left in source as if a section"
|
|
888
|
+
assert_grep ' ## Intent' "$sub/data/backlog.md" "indented ## Intent did not arrive at destination"
|
|
889
|
+
|
|
890
|
+
assert_grep 'keep-a' "$home/data/backlog.md" "keep-a was wrongly removed"
|
|
891
|
+
assert_grep ' keep-a body line' "$home/data/backlog.md" "keep-a body was disturbed"
|
|
892
|
+
assert_grep 'keep-b' "$home/data/backlog.md" "keep-b was wrongly removed"
|
|
893
|
+
assert_grep ' keep-b body stays' "$home/data/backlog.md" "keep-b body was disturbed"
|
|
894
|
+
|
|
895
|
+
# keep-a's body must not have grown the orphaned lines of body-item.
|
|
896
|
+
local keep_a_block
|
|
897
|
+
keep_a_block=$(extract_item_block "$home/data/backlog.md" keep-a)
|
|
898
|
+
assert_block_equals "keep-a block must not absorb orphaned body-item lines" \
|
|
899
|
+
$'- [ ] keep-a - stays first (repo: alpha)\n keep-a body line' \
|
|
900
|
+
"$keep_a_block"
|
|
901
|
+
|
|
902
|
+
pass "body followed by another item moves intact with no source orphans"
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
test_body_moves_when_followed_by_section_heading() {
|
|
906
|
+
local home="$TMP_ROOT/body-section-main"
|
|
907
|
+
local sub="$TMP_ROOT/body-section-sub"
|
|
908
|
+
setup_homes "$home" "$sub"
|
|
909
|
+
|
|
910
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
911
|
+
## Queued
|
|
912
|
+
- [ ] section-tail - body ends at section (repo: alpha)
|
|
913
|
+
last queued body
|
|
914
|
+
## Intent
|
|
915
|
+
still body until column-0 section
|
|
916
|
+
|
|
917
|
+
## Done
|
|
918
|
+
- [x] old-task - shipped - local main (merged 2026-07-01)
|
|
919
|
+
EOF
|
|
920
|
+
|
|
921
|
+
local expected_block
|
|
922
|
+
expected_block=$(extract_item_block "$home/data/backlog.md" section-tail)
|
|
923
|
+
|
|
924
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design section-tail >/dev/null \
|
|
925
|
+
|| fail "handoff of body-followed-by-section failed"
|
|
926
|
+
|
|
927
|
+
local dest_block
|
|
928
|
+
dest_block=$(extract_item_block "$sub/data/backlog.md" section-tail)
|
|
929
|
+
assert_block_equals "destination body block mismatch after section-boundary handoff" \
|
|
930
|
+
"$expected_block" "$dest_block"
|
|
931
|
+
|
|
932
|
+
assert_no_grep 'section-tail' "$home/data/backlog.md" "section-tail still in source"
|
|
933
|
+
assert_no_grep 'last queued body' "$home/data/backlog.md" "body orphaned before ## Done"
|
|
934
|
+
assert_no_grep 'still body until' "$home/data/backlog.md" "body after ## Intent orphaned"
|
|
935
|
+
assert_grep 'old-task' "$home/data/backlog.md" "Done section item was disturbed"
|
|
936
|
+
assert_grep '## Done' "$home/data/backlog.md" "Done section heading was disturbed"
|
|
937
|
+
|
|
938
|
+
pass "body followed by section heading moves intact; section stays"
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
test_body_moves_when_last_lines_of_file() {
|
|
942
|
+
local home="$TMP_ROOT/body-eof-main"
|
|
943
|
+
local sub="$TMP_ROOT/body-eof-sub"
|
|
944
|
+
setup_homes "$home" "$sub"
|
|
945
|
+
|
|
946
|
+
# A source item that ends the file with no trailing newline is a valid shape;
|
|
947
|
+
# printf builds that deliberately. It must move whole, indented ## line
|
|
948
|
+
# included, into the destination the handoff seeds.
|
|
949
|
+
{
|
|
950
|
+
printf '%s\n' '## Queued'
|
|
951
|
+
printf '%s\n' '- [ ] eof-item - ends the file (repo: alpha)'
|
|
952
|
+
printf '%s\n' ' eof body line one'
|
|
953
|
+
printf '%s\n' ' ## Intent'
|
|
954
|
+
printf '%s' ' eof body line two'
|
|
955
|
+
} > "$home/data/backlog.md"
|
|
956
|
+
# tasks-axi owns the destination format: the moved block lands under ## Queued
|
|
957
|
+
# in the standard three-section scaffold the handoff seeds for a fresh home.
|
|
958
|
+
local expected_destination="$TMP_ROOT/body-eof-expected.md"
|
|
959
|
+
{
|
|
960
|
+
printf '%s\n' '## In flight'
|
|
961
|
+
printf '%s\n' ''
|
|
962
|
+
printf '%s\n' '## Queued'
|
|
963
|
+
printf '%s\n' '- [ ] eof-item - ends the file (repo: alpha)'
|
|
964
|
+
printf '%s\n' ' eof body line one'
|
|
965
|
+
printf '%s\n' ' ## Intent'
|
|
966
|
+
printf '%s\n' ' eof body line two'
|
|
967
|
+
printf '%s\n' ''
|
|
968
|
+
printf '%s\n' '## Done'
|
|
969
|
+
} > "$expected_destination"
|
|
970
|
+
|
|
971
|
+
local expected_block
|
|
972
|
+
expected_block=$(extract_item_block "$home/data/backlog.md" eof-item)
|
|
973
|
+
|
|
974
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design eof-item >/dev/null \
|
|
975
|
+
|| fail "handoff of EOF body item failed"
|
|
976
|
+
|
|
977
|
+
local dest_block
|
|
978
|
+
dest_block=$(extract_item_block "$sub/data/backlog.md" eof-item)
|
|
979
|
+
assert_block_equals "destination body block mismatch for EOF item" \
|
|
980
|
+
"$expected_block" "$dest_block"
|
|
981
|
+
cmp -s "$expected_destination" "$sub/data/backlog.md" \
|
|
982
|
+
|| fail "EOF item did not land byte-exact under the seeded destination scaffold"
|
|
983
|
+
|
|
984
|
+
# Source should have no item residual - only the section heading remains.
|
|
985
|
+
if grep -E 'eof-item|eof body|## Intent' "$home/data/backlog.md" >/dev/null; then
|
|
986
|
+
fail "EOF item left residual header or body lines in source"
|
|
987
|
+
fi
|
|
988
|
+
assert_grep '## Queued' "$home/data/backlog.md" "Queued section heading was lost"
|
|
989
|
+
|
|
990
|
+
pass "body as last lines of the file moves intact"
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
test_eof_body_before_seeded_destination_section_keeps_boundary() {
|
|
994
|
+
local home="$TMP_ROOT/body-eof-seeded-main"
|
|
995
|
+
local sub="$TMP_ROOT/body-eof-seeded-sub"
|
|
996
|
+
setup_homes "$home" "$sub"
|
|
997
|
+
|
|
998
|
+
{
|
|
999
|
+
printf '%s\n' '## Queued'
|
|
1000
|
+
printf '%s\n' '- [ ] seeded-eof-item - ends the file (repo: alpha)'
|
|
1001
|
+
printf '%s\n' ' seeded eof body one'
|
|
1002
|
+
printf '%s' ' seeded eof body two'
|
|
1003
|
+
} > "$home/data/backlog.md"
|
|
1004
|
+
# tasks-axi owns the destination whitespace: the moved block sits directly
|
|
1005
|
+
# under ## Queued with the section separator before the following ## Done, and
|
|
1006
|
+
# the EOF body stays a clean line above that heading (its boundary is kept).
|
|
1007
|
+
local expected_destination="$TMP_ROOT/body-eof-seeded-expected.md"
|
|
1008
|
+
{
|
|
1009
|
+
printf '%s\n' '## In flight'
|
|
1010
|
+
printf '%s\n' ''
|
|
1011
|
+
printf '%s\n' '## Queued'
|
|
1012
|
+
printf '%s\n' '- [ ] seeded-eof-item - ends the file (repo: alpha)'
|
|
1013
|
+
printf '%s\n' ' seeded eof body one'
|
|
1014
|
+
printf '%s\n' ' seeded eof body two'
|
|
1015
|
+
printf '%s\n' ''
|
|
1016
|
+
printf '%s\n' '## Done'
|
|
1017
|
+
} > "$expected_destination"
|
|
1018
|
+
|
|
1019
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design seeded-eof-item >/dev/null \
|
|
1020
|
+
|| fail "handoff of EOF body into seeded backlog failed"
|
|
1021
|
+
|
|
1022
|
+
cmp -s "$expected_destination" "$sub/data/backlog.md" \
|
|
1023
|
+
|| fail "EOF body did not remain separate from the seeded ## Done heading"
|
|
1024
|
+
|
|
1025
|
+
pass "EOF body before a seeded destination section keeps its boundary"
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
test_untouched_eof_line_preserves_terminator() {
|
|
1029
|
+
local home="$TMP_ROOT/untouched-eof-main"
|
|
1030
|
+
local sub="$TMP_ROOT/untouched-eof-sub"
|
|
1031
|
+
setup_homes "$home" "$sub"
|
|
1032
|
+
|
|
1033
|
+
{
|
|
1034
|
+
printf '%s\n' '## Queued'
|
|
1035
|
+
printf '%s\n' '- [ ] move-item - remove this block (repo: alpha)'
|
|
1036
|
+
printf '%s\n' ' move body'
|
|
1037
|
+
printf '%s\n' '- [ ] keep-item - retain this block (repo: beta)'
|
|
1038
|
+
printf '%s' ' keep body without a final newline'
|
|
1039
|
+
} > "$home/data/backlog.md"
|
|
1040
|
+
local expected_source="$TMP_ROOT/untouched-eof-expected.md"
|
|
1041
|
+
{
|
|
1042
|
+
printf '%s\n' '## Queued'
|
|
1043
|
+
printf '%s\n' '- [ ] keep-item - retain this block (repo: beta)'
|
|
1044
|
+
printf '%s' ' keep body without a final newline'
|
|
1045
|
+
} > "$expected_source"
|
|
1046
|
+
|
|
1047
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design move-item >/dev/null \
|
|
1048
|
+
|| fail "handoff before untouched EOF preservation check failed"
|
|
1049
|
+
|
|
1050
|
+
cmp -s "$expected_source" "$home/data/backlog.md" \
|
|
1051
|
+
|| fail "handoff changed an untouched final-record terminator"
|
|
1052
|
+
|
|
1053
|
+
pass "untouched EOF line preserves its original terminator"
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
test_body_handoff_is_idempotent() {
|
|
1057
|
+
local home="$TMP_ROOT/body-idem-main"
|
|
1058
|
+
local sub="$TMP_ROOT/body-idem-sub"
|
|
1059
|
+
setup_homes "$home" "$sub"
|
|
1060
|
+
|
|
1061
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1062
|
+
## Queued
|
|
1063
|
+
- [ ] neighbor - untouched (repo: alpha)
|
|
1064
|
+
neighbor body
|
|
1065
|
+
- [ ] idem-item - multi-line for re-run (repo: alpha)
|
|
1066
|
+
## Intent
|
|
1067
|
+
Idempotent body must not duplicate.
|
|
1068
|
+
final note
|
|
1069
|
+
|
|
1070
|
+
## Done
|
|
1071
|
+
EOF
|
|
1072
|
+
|
|
1073
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design idem-item >/dev/null \
|
|
1074
|
+
|| fail "first handoff of body-carrying item failed"
|
|
1075
|
+
|
|
1076
|
+
local main_after dest_after
|
|
1077
|
+
main_after=$(cat "$home/data/backlog.md")
|
|
1078
|
+
dest_after=$(cat "$sub/data/backlog.md")
|
|
1079
|
+
|
|
1080
|
+
local out
|
|
1081
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design idem-item 2>&1) \
|
|
1082
|
+
|| fail "idempotent re-run of body-carrying item failed"
|
|
1083
|
+
assert_contains "$out" "already present" "re-run did not report skip of already-present key"
|
|
1084
|
+
|
|
1085
|
+
[ "$main_after" = "$(cat "$home/data/backlog.md")" ] \
|
|
1086
|
+
|| fail "idempotent re-run mutated the main backlog"
|
|
1087
|
+
[ "$dest_after" = "$(cat "$sub/data/backlog.md")" ] \
|
|
1088
|
+
|| fail "idempotent re-run mutated the secondmate backlog"
|
|
1089
|
+
|
|
1090
|
+
local count
|
|
1091
|
+
count=$(grep -cF -- '- [ ] idem-item - multi-line for re-run (repo: alpha)' "$sub/data/backlog.md")
|
|
1092
|
+
[ "$count" -eq 1 ] || fail "idempotent re-run duplicated the item header (count=$count)"
|
|
1093
|
+
count=$(grep -cF -- 'Idempotent body must not duplicate.' "$sub/data/backlog.md")
|
|
1094
|
+
[ "$count" -eq 1 ] || fail "idempotent re-run duplicated a body line (count=$count)"
|
|
1095
|
+
count=$(grep -cF -- ' ## Intent' "$sub/data/backlog.md")
|
|
1096
|
+
[ "$count" -eq 1 ] || fail "idempotent re-run duplicated indented ## Intent (count=$count)"
|
|
1097
|
+
|
|
1098
|
+
assert_grep 'neighbor' "$home/data/backlog.md" "neighbor item was disturbed by re-run"
|
|
1099
|
+
assert_grep ' neighbor body' "$home/data/backlog.md" "neighbor body was disturbed by re-run"
|
|
1100
|
+
|
|
1101
|
+
pass "body-carrying handoff is idempotent: re-run changes nothing"
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
test_noncanonical_indented_continuations_refuse_without_changes() {
|
|
1105
|
+
local home="$TMP_ROOT/noncanonical-main"
|
|
1106
|
+
local sub="$TMP_ROOT/noncanonical-sub"
|
|
1107
|
+
setup_homes "$home" "$sub"
|
|
1108
|
+
|
|
1109
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1110
|
+
## Queued
|
|
1111
|
+
- [ ] malformed-body - must not orphan continuations (repo: alpha)
|
|
1112
|
+
one-space continuation
|
|
1113
|
+
EOF
|
|
1114
|
+
printf '\ttab continuation\n' >> "$home/data/backlog.md"
|
|
1115
|
+
cat >> "$home/data/backlog.md" <<'EOF'
|
|
1116
|
+
- [ ] untouched-item - remains in the main backlog (repo: beta)
|
|
1117
|
+
canonical body
|
|
1118
|
+
EOF
|
|
1119
|
+
cat > "$sub/data/backlog.md" <<'EOF'
|
|
1120
|
+
## Queued
|
|
1121
|
+
- [ ] resident-item - remains in the secondmate backlog (repo: alpha)
|
|
1122
|
+
resident body
|
|
1123
|
+
EOF
|
|
1124
|
+
|
|
1125
|
+
local source_before="$TMP_ROOT/noncanonical-source-before.md"
|
|
1126
|
+
local destination_before="$TMP_ROOT/noncanonical-destination-before.md"
|
|
1127
|
+
local out
|
|
1128
|
+
cp "$home/data/backlog.md" "$source_before"
|
|
1129
|
+
cp "$sub/data/backlog.md" "$destination_before"
|
|
1130
|
+
|
|
1131
|
+
if out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design malformed-body 2>&1); then
|
|
1132
|
+
fail "handoff accepted a noncanonical indented continuation"
|
|
1133
|
+
fi
|
|
1134
|
+
|
|
1135
|
+
assert_contains "$out" "malformed-body" "refusal did not name the selected item"
|
|
1136
|
+
assert_contains "$out" "one-space continuation" "refusal did not name the one-space continuation"
|
|
1137
|
+
assert_contains "$out" "tab continuation" "refusal did not name the tab continuation"
|
|
1138
|
+
cmp -s "$source_before" "$home/data/backlog.md" \
|
|
1139
|
+
|| fail "noncanonical-continuation refusal changed the main backlog"
|
|
1140
|
+
cmp -s "$destination_before" "$sub/data/backlog.md" \
|
|
1141
|
+
|| fail "noncanonical-continuation refusal changed the secondmate backlog"
|
|
1142
|
+
|
|
1143
|
+
pass "noncanonical one-space and tab continuations refuse without changes"
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
test_indented_heading_is_not_section_boundary() {
|
|
1147
|
+
# Standalone focus on the tokenizer trap that caused the live incident.
|
|
1148
|
+
local home="$TMP_ROOT/intent-trap-main"
|
|
1149
|
+
local sub="$TMP_ROOT/intent-trap-sub"
|
|
1150
|
+
setup_homes "$home" "$sub" design
|
|
1151
|
+
|
|
1152
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1153
|
+
## Queued
|
|
1154
|
+
- [ ] ha-codex-fast-default-4e - harness default work (repo: firstmate)
|
|
1155
|
+
Context for the secondmate.
|
|
1156
|
+
## Intent
|
|
1157
|
+
Deliver the full spec, not the title alone.
|
|
1158
|
+
## Acceptance
|
|
1159
|
+
- body survives handoff
|
|
1160
|
+
- ## headings inside body stay body
|
|
1161
|
+
- [ ] next-item - after the trap (repo: firstmate)
|
|
1162
|
+
EOF
|
|
1163
|
+
|
|
1164
|
+
local expected_block
|
|
1165
|
+
expected_block=$(extract_item_block "$home/data/backlog.md" ha-codex-fast-default-4e)
|
|
1166
|
+
|
|
1167
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design ha-codex-fast-default-4e >/dev/null \
|
|
1168
|
+
|| fail "handoff of ## Intent body item failed"
|
|
1169
|
+
|
|
1170
|
+
local dest_block
|
|
1171
|
+
dest_block=$(extract_item_block "$sub/data/backlog.md" ha-codex-fast-default-4e)
|
|
1172
|
+
assert_block_equals "tokenizer trap: indented ## lines must move with the item" \
|
|
1173
|
+
"$expected_block" "$dest_block"
|
|
1174
|
+
|
|
1175
|
+
# Source must not treat ## Intent / ## Acceptance as new sections that split the file.
|
|
1176
|
+
if grep -E 'ha-codex-fast-default-4e|Deliver the full spec|body survives handoff' \
|
|
1177
|
+
"$home/data/backlog.md" >/dev/null; then
|
|
1178
|
+
fail "tokenizer trap left item fragments in the source backlog"
|
|
1179
|
+
fi
|
|
1180
|
+
assert_grep 'next-item' "$home/data/backlog.md" "following item was lost after ## Intent body"
|
|
1181
|
+
# Exactly one real Queued section; no spurious column-0 ## Intent section invented.
|
|
1182
|
+
local heading_count
|
|
1183
|
+
heading_count=$(grep -cE '^## ' "$home/data/backlog.md")
|
|
1184
|
+
[ "$heading_count" -eq 1 ] || fail "source gained extra column-0 ## headings (count=$heading_count)"
|
|
1185
|
+
heading_count=$(grep -cE '^## ' "$sub/data/backlog.md")
|
|
1186
|
+
# sub scaffold has In flight / Queued / Done
|
|
1187
|
+
[ "$heading_count" -eq 3 ] || fail "destination has unexpected ## section count (count=$heading_count)"
|
|
1188
|
+
|
|
1189
|
+
pass "indented ## Intent / ## Acceptance are body, not section boundaries"
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
test_multi_paragraph_body_with_internal_blanks_moves_whole() {
|
|
1193
|
+
# The live re-orphan risk: a blank line inside a multi-paragraph body must not
|
|
1194
|
+
# terminate the block and strand the paragraphs after it. Blank lines are body
|
|
1195
|
+
# content and move with the item; only the next item header or a column-0
|
|
1196
|
+
# section heading ends the block. Includes an indented ## after a blank.
|
|
1197
|
+
local home="$TMP_ROOT/multi-para-main"
|
|
1198
|
+
local sub="$TMP_ROOT/multi-para-sub"
|
|
1199
|
+
setup_homes "$home" "$sub"
|
|
1200
|
+
|
|
1201
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1202
|
+
## Queued
|
|
1203
|
+
- [ ] before-multi - stays put (repo: alpha)
|
|
1204
|
+
before body
|
|
1205
|
+
- [ ] multi-para - multi-paragraph body (repo: alpha)
|
|
1206
|
+
First paragraph line.
|
|
1207
|
+
|
|
1208
|
+
Second paragraph after a blank.
|
|
1209
|
+
## Intent
|
|
1210
|
+
|
|
1211
|
+
Indented heading then blank then more.
|
|
1212
|
+
final line
|
|
1213
|
+
- [ ] after-multi - subsequent item (repo: alpha)
|
|
1214
|
+
after body
|
|
1215
|
+
|
|
1216
|
+
## Done
|
|
1217
|
+
EOF
|
|
1218
|
+
|
|
1219
|
+
local expected_block
|
|
1220
|
+
expected_block=$(extract_item_block "$home/data/backlog.md" multi-para)
|
|
1221
|
+
|
|
1222
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design multi-para >/dev/null \
|
|
1223
|
+
|| fail "handoff of multi-paragraph body failed"
|
|
1224
|
+
|
|
1225
|
+
local dest_block
|
|
1226
|
+
dest_block=$(extract_item_block "$sub/data/backlog.md" multi-para)
|
|
1227
|
+
assert_block_equals "multi-paragraph body with internal blanks must move whole" \
|
|
1228
|
+
"$expected_block" "$dest_block"
|
|
1229
|
+
|
|
1230
|
+
# Every body line, including the ones after each internal blank, must leave the source.
|
|
1231
|
+
assert_no_grep 'multi-para' "$home/data/backlog.md" "multi-para header still in source"
|
|
1232
|
+
assert_no_grep 'First paragraph line' "$home/data/backlog.md" "first paragraph orphaned in source"
|
|
1233
|
+
assert_no_grep 'Second paragraph after a blank' "$home/data/backlog.md" "post-blank paragraph orphaned in source"
|
|
1234
|
+
assert_no_grep 'Indented heading then blank then more' "$home/data/backlog.md" "post-blank body orphaned in source"
|
|
1235
|
+
assert_no_grep 'final line' "$home/data/backlog.md" "trailing body orphaned in source"
|
|
1236
|
+
assert_no_grep '## Intent' "$home/data/backlog.md" "indented ## Intent left in source as if a section"
|
|
1237
|
+
|
|
1238
|
+
# The post-blank paragraphs must actually arrive at the destination.
|
|
1239
|
+
assert_grep ' Second paragraph after a blank.' "$sub/data/backlog.md" "post-blank paragraph did not arrive"
|
|
1240
|
+
assert_grep ' Indented heading then blank then more.' "$sub/data/backlog.md" "post-blank body did not arrive"
|
|
1241
|
+
assert_grep ' ## Intent' "$sub/data/backlog.md" "indented ## Intent did not arrive at destination"
|
|
1242
|
+
|
|
1243
|
+
# Neighbors on both sides stay intact.
|
|
1244
|
+
assert_grep 'before-multi' "$home/data/backlog.md" "before-multi was wrongly removed"
|
|
1245
|
+
assert_grep ' before body' "$home/data/backlog.md" "before-multi body was disturbed"
|
|
1246
|
+
assert_grep 'after-multi' "$home/data/backlog.md" "after-multi was wrongly removed"
|
|
1247
|
+
assert_grep ' after body' "$home/data/backlog.md" "after-multi body was disturbed"
|
|
1248
|
+
|
|
1249
|
+
# Idempotent re-run: already present, no duplication, no mutation.
|
|
1250
|
+
local main_after dest_after
|
|
1251
|
+
main_after=$(cat "$home/data/backlog.md")
|
|
1252
|
+
dest_after=$(cat "$sub/data/backlog.md")
|
|
1253
|
+
local out
|
|
1254
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" design multi-para 2>&1) \
|
|
1255
|
+
|| fail "idempotent re-run of multi-paragraph body failed"
|
|
1256
|
+
assert_contains "$out" "already present" "re-run did not report skip of already-present key"
|
|
1257
|
+
[ "$main_after" = "$(cat "$home/data/backlog.md")" ] \
|
|
1258
|
+
|| fail "idempotent re-run mutated the main backlog"
|
|
1259
|
+
[ "$dest_after" = "$(cat "$sub/data/backlog.md")" ] \
|
|
1260
|
+
|| fail "idempotent re-run mutated the secondmate backlog"
|
|
1261
|
+
local count
|
|
1262
|
+
count=$(grep -cF -- ' Second paragraph after a blank.' "$sub/data/backlog.md")
|
|
1263
|
+
[ "$count" -eq 1 ] || fail "idempotent re-run duplicated a post-blank paragraph (count=$count)"
|
|
1264
|
+
|
|
1265
|
+
pass "multi-paragraph body with internal blank lines moves whole and is idempotent"
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
# Registry lines may carry parentheticals in the summary before the structured
|
|
1269
|
+
# (home: ...) field (e.g. "(id is legacy)"). The home extractor must still find
|
|
1270
|
+
# the field; the old ^[^(]* regex treated those entries as home-less.
|
|
1271
|
+
test_registry_home_with_pre_home_parentheses() {
|
|
1272
|
+
local home="$TMP_ROOT/reg-parens-main"
|
|
1273
|
+
local sub="$TMP_ROOT/reg-parens-sub"
|
|
1274
|
+
local id=oss-triage-t4
|
|
1275
|
+
setup_homes "$home" "$sub" "$id"
|
|
1276
|
+
local sub_abs
|
|
1277
|
+
sub_abs=$(cd "$sub" && pwd -P)
|
|
1278
|
+
# Prose parentheses before (home: ...) and punctuation inside scope match the live registry shape.
|
|
1279
|
+
printf -- '- %s - issue triage (id is legacy) (home: %s; scope: issue triage (child); semicolon is meaningful; projects: alpha; added 2026-07-09)\n' \
|
|
1280
|
+
"$id" "$sub_abs" > "$home/data/secondmates.md"
|
|
1281
|
+
FM_HOME="$home" "$ROOT/bin/fm-home-seed.sh" validate >/dev/null \
|
|
1282
|
+
|| fail "home-seed validation rejected punctuation-bearing registry fields"
|
|
1283
|
+
|
|
1284
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1285
|
+
## Queued
|
|
1286
|
+
- [ ] paren-item - should hand off (repo: alpha)
|
|
1287
|
+
body line
|
|
1288
|
+
|
|
1289
|
+
## Done
|
|
1290
|
+
EOF
|
|
1291
|
+
|
|
1292
|
+
FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" "$id" paren-item >/dev/null \
|
|
1293
|
+
|| fail "handoff failed for registry entry with parentheses before (home: ...)"
|
|
1294
|
+
|
|
1295
|
+
assert_grep 'paren-item' "$sub/data/backlog.md" \
|
|
1296
|
+
"item did not arrive when registry summary had pre-home parentheses"
|
|
1297
|
+
assert_no_grep 'paren-item' "$home/data/backlog.md" \
|
|
1298
|
+
"item still in source after handoff with pre-home parentheses"
|
|
1299
|
+
|
|
1300
|
+
pass "registry home parses when summary has parentheses before (home: ...)"
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
# An entry that genuinely lacks (home: ...) must still fail cleanly (empty parse
|
|
1304
|
+
# surfaces as "has no home"), not succeed or mis-parse prose.
|
|
1305
|
+
test_registry_home_missing_field_fails_cleanly() {
|
|
1306
|
+
local home="$TMP_ROOT/reg-nohome-main"
|
|
1307
|
+
local sub="$TMP_ROOT/reg-nohome-sub"
|
|
1308
|
+
local id=no-home-mate
|
|
1309
|
+
mkdir -p "$home/data" "$home/state"
|
|
1310
|
+
seed_secondmate_home_marker "$sub" "$id"
|
|
1311
|
+
# Registered, but no structured (home: ...) field at all.
|
|
1312
|
+
printf -- '- %s - charter only (scope prose mentions home: /tmp/ignored-path)\n' \
|
|
1313
|
+
"$id" > "$home/data/secondmates.md"
|
|
1314
|
+
|
|
1315
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1316
|
+
## Queued
|
|
1317
|
+
- [ ] orphan-item - never moves (repo: alpha)
|
|
1318
|
+
|
|
1319
|
+
## Done
|
|
1320
|
+
EOF
|
|
1321
|
+
|
|
1322
|
+
local out rc=0
|
|
1323
|
+
out=$(FM_HOME="$home" "$ROOT/bin/fm-backlog-handoff.sh" "$id" orphan-item 2>&1) || rc=$?
|
|
1324
|
+
[ "$rc" -ne 0 ] || fail "handoff succeeded for registry entry with no (home: ...) field"
|
|
1325
|
+
assert_contains "$out" "has no home" \
|
|
1326
|
+
"missing (home: ...) field did not report the clean 'has no home' error"
|
|
1327
|
+
assert_grep 'orphan-item' "$home/data/backlog.md" \
|
|
1328
|
+
"source backlog was mutated despite missing home"
|
|
1329
|
+
[ ! -f "$sub/data/backlog.md" ] || ! grep -q 'orphan-item' "$sub/data/backlog.md" 2>/dev/null \
|
|
1330
|
+
|| fail "item appeared in secondmate backlog despite missing home"
|
|
1331
|
+
|
|
1332
|
+
pass "registry entry without (home: ...) fails cleanly with has no home"
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
test_handoff_wakes_live_local_receiver
|
|
1336
|
+
test_failed_wake_retries_when_the_item_is_already_present
|
|
1337
|
+
test_known_receiver_failure_remains_retryable_after_grace
|
|
1338
|
+
test_known_failure_restores_retry_after_reconciliation_race
|
|
1339
|
+
test_move_crash_keeps_wake_pending_for_recovery
|
|
1340
|
+
test_pre_move_crash_does_not_wake_until_move_lands
|
|
1341
|
+
test_delivery_confirmation_crash_does_not_resend
|
|
1342
|
+
test_unresolved_delivery_attempt_refuses_immediate_resend
|
|
1343
|
+
test_concurrent_local_handoffs_serialize_move_and_wake
|
|
1344
|
+
test_local_teardown_waits_for_handoff_wake
|
|
1345
|
+
test_local_teardown_preserves_wake_when_home_removal_fails
|
|
1346
|
+
test_body_moves_when_followed_by_another_item
|
|
1347
|
+
test_body_moves_when_followed_by_section_heading
|
|
1348
|
+
test_multi_paragraph_body_with_internal_blanks_moves_whole
|
|
1349
|
+
test_body_moves_when_last_lines_of_file
|
|
1350
|
+
test_eof_body_before_seeded_destination_section_keeps_boundary
|
|
1351
|
+
test_untouched_eof_line_preserves_terminator
|
|
1352
|
+
test_body_handoff_is_idempotent
|
|
1353
|
+
test_noncanonical_indented_continuations_refuse_without_changes
|
|
1354
|
+
test_indented_heading_is_not_section_boundary
|
|
1355
|
+
test_registry_home_with_pre_home_parentheses
|
|
1356
|
+
test_registry_home_missing_field_fails_cleanly
|
|
1357
|
+
test_handoff_warns_when_a_moved_item_still_owes_a_public_reply
|
|
1358
|
+
test_handoff_is_silent_about_public_commitments_without_the_relay
|
|
1359
|
+
|
|
1360
|
+
echo "ALL TESTS PASSED"
|