@zerwiz/ymir 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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 +290 -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 +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -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/config/tailscale-sync.yaml +27 -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/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -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 +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -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 +158 -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 +673 -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 +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -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 +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -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 +96 -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 +175 -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 +360 -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 +70 -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 +52 -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 +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -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 +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -59
- 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 +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -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 +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -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 +38 -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-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -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 +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -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-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -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-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -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 +91 -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 +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -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 +641 -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-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -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 +1056 -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 +44 -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 +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1568 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Bootstrap detection, best-effort fleet refresh/prune, and installs.
|
|
3
|
+
# Usage: fm-bootstrap.sh
|
|
4
|
+
# Detect: prints one line per actionable problem, or an explicit
|
|
5
|
+
# BOOTSTRAP_INFO no-action fact for completed benign bootstrap work, and
|
|
6
|
+
# exits 0.
|
|
7
|
+
# Silent = all good.
|
|
8
|
+
# Lines: "MISSING: <tool> (install: <command>)",
|
|
9
|
+
# "MISSING_MANUAL: <tool> (instructions: <url>)", "NEEDS_GH_AUTH",
|
|
10
|
+
# "BACKEND_INVALID: <name> (known: <names>)",
|
|
11
|
+
# "STARTUP_MEMORY_BUDGET: invalid config/startup-memory-budget - <reason>",
|
|
12
|
+
# "CREW_DISPATCH: invalid config/crew-dispatch.json - <reason>",
|
|
13
|
+
# "FLEET_SYNC: <repo>: skipped|recovered|STUCK: <detail>",
|
|
14
|
+
# "HOME_SUMMARY: <ledger never published|not republished since
|
|
15
|
+
# <stamp>>; <n> failed attempt(s) ... last: <recorded failure>",
|
|
16
|
+
# "BACKLOG_RECONCILE: <id>: <what this home could not reconcile>",
|
|
17
|
+
# "TANGLE: <remediation>",
|
|
18
|
+
# "SECONDMATE_SYNC: secondmate <id>: skipped: <reason>",
|
|
19
|
+
# "NUDGE_SECONDMATES: secondmate <id>: send failed: <reason>",
|
|
20
|
+
# "BOOTSTRAP_INFO: nudged fm-<id> with '<message>'",
|
|
21
|
+
# "SECONDMATE_LIVENESS: secondmate <id>: skipped: <reason>|respawn failed after <cause>: <reason>",
|
|
22
|
+
# "SECONDMATE_HANDOFF: secondmate <id>: pending delivery: <n> item(s)",
|
|
23
|
+
# "FMX: X mode on ..." or "FMX: X mode off ...".
|
|
24
|
+
# When a RUNNING local secondmate worktree is fast-forwarded to
|
|
25
|
+
# firstmate's own current default-branch commit, that update is a
|
|
26
|
+
# purely local fast-forward and never an origin fetch. Remote routes
|
|
27
|
+
# instead converge the persistent home to their configured remote code
|
|
28
|
+
# root. If either placement changes its loaded instruction surface
|
|
29
|
+
# (AGENTS.md, bin/, or .agents/skills/), bootstrap immediately nudges it
|
|
30
|
+
# via FM_HOME=<active-home> bin/fm-send.sh fm-<id> so meta resolves the
|
|
31
|
+
# current route and the standard from-firstmate marker is applied. A
|
|
32
|
+
# successful send prints one BOOTSTRAP_INFO line with the exact target
|
|
33
|
+
# and message sent; a failed send leaves an idempotent retry marker
|
|
34
|
+
# under state/.secondmate-nudge-pending/ and prints an actionable
|
|
35
|
+
# NUDGE_SECONDMATES line.
|
|
36
|
+
# Already-current or no-instruction-change homes are silently left alone.
|
|
37
|
+
# The secondmate sweep also propagates declared inherited local material
|
|
38
|
+
# into each validated live secondmate home.
|
|
39
|
+
# SECONDMATE_SYNC lines report actionable skipped placement-specific
|
|
40
|
+
# syncs or inheritance failures for live secondmate homes, plus
|
|
41
|
+
# quarantine diagnostics for divergent shared captain-preference
|
|
42
|
+
# copies; no-op/current and successful updates stay quiet.
|
|
43
|
+
# SECONDMATE_LIVENESS lines report only actionable failures from the
|
|
44
|
+
# recovery-grade state owned by bin/fm-backend.sh's
|
|
45
|
+
# fm_backend_agent_state: skipped distinguishes an existing ambiguous
|
|
46
|
+
# process, an unreadable target, and an unverified backend; respawn
|
|
47
|
+
# failed names whether the endpoint was missing or agent-less.
|
|
48
|
+
# Already-live and successfully relaunched secondmates are silent
|
|
49
|
+
# unless FM_BOOTSTRAP_VERBOSE_FACTS=1 requests BOOTSTRAP_INFO facts.
|
|
50
|
+
# A TANGLE line means the firstmate primary checkout (FM_ROOT) is stranded
|
|
51
|
+
# on a feature branch instead of its default branch - a crewmate's work
|
|
52
|
+
# landed in the primary instead of its own worktree; restore it per the line.
|
|
53
|
+
# treehouse is also MISSING when its installed version lacks
|
|
54
|
+
# "treehouse get --lease" support.
|
|
55
|
+
# no-mistakes is also MISSING when its installed version is older than
|
|
56
|
+
# 1.46.0 (structured pipeline attestation floor; see CONTRIBUTING.md).
|
|
57
|
+
# The AXI-family floor policy is owned beside GH_AXI_MIN and
|
|
58
|
+
# LAVISH_AXI_MIN below; the per-tool owners point there. An installed
|
|
59
|
+
# build below its floor reports MISSING like no-mistakes, so the operator
|
|
60
|
+
# is asked to upgrade rather than silently running an older tool.
|
|
61
|
+
# tasks-axi feature probes remain a separate defense-in-depth check.
|
|
62
|
+
# tasks-axi and quota-axi are required bootstrap tools (same class as
|
|
63
|
+
# lavish-axi). A compatible tasks-axi default backend is silent.
|
|
64
|
+
# quota-axi is required for the agent-owned dispatch-profile array
|
|
65
|
+
# procedure in AGENTS.md section 4 and
|
|
66
|
+
# .agents/skills/quota-array-dispatch/SKILL.md.
|
|
67
|
+
# On a primary home, the locked mutable path materializes the visible
|
|
68
|
+
# default config/startup-memory-budget=7500 when absent. It never
|
|
69
|
+
# guesses at malformed or unsafe existing files, and secondmate homes
|
|
70
|
+
# await the primary-authoritative inherited value instead of creating
|
|
71
|
+
# their own.
|
|
72
|
+
# X mode is OPTIONAL and inert unless FM_HOME/.env has a non-empty
|
|
73
|
+
# FMX_PAIRING_TOKEN. When opted in, bootstrap requires curl+jq, writes
|
|
74
|
+
# the relay poll shim and 30s cadence config, and prints an FMX line.
|
|
75
|
+
# Fleet sync fetches, fast-forwards safe default-branch states, reports
|
|
76
|
+
# recovered and STUCK clone drift, and prunes gone local branches; it is
|
|
77
|
+
# bounded by FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT when it is a non-empty
|
|
78
|
+
# numeric override, while non-numeric values fall back to 20s.
|
|
79
|
+
# When the override is unset or blank, the timeout is
|
|
80
|
+
# max(20, 5 + 3 * origin-backed project clone count). A timed-out
|
|
81
|
+
# refresh relays any completed fm-fleet-sync.sh output before the
|
|
82
|
+
# aggregate timeout skip line with timeout and elapsed seconds.
|
|
83
|
+
# Set FM_FLEET_PRUNE=0 to skip branch pruning during that refresh.
|
|
84
|
+
# BACKLOG_RECONCILE lines report what backlog_record_reconcile could not
|
|
85
|
+
# settle in THIS home. Every ordinary dispatch and completion now moves
|
|
86
|
+
# the backlog row inside the script that moves the task's record
|
|
87
|
+
# (bin/fm-backlog-transition-lib.sh), so this sweep exists for the
|
|
88
|
+
# crash window inside those scripts and for drift a home was already
|
|
89
|
+
# carrying: it finishes the authoritative close an interrupted cleanup
|
|
90
|
+
# recorded, and marks In flight any item this home already owns a worker
|
|
91
|
+
# for. The worker-record sweep never starts a captain-held or closed
|
|
92
|
+
# item, and reconciliation never reads or writes another home; the fleet
|
|
93
|
+
# snapshot's classifier and
|
|
94
|
+
# bin/fm-secondmate-reconcile.sh's nudge stay as backstops. Replayed
|
|
95
|
+
# closes and restored In-flight rows print BOOTSTRAP_INFO facts.
|
|
96
|
+
# Set FM_BOOTSTRAP_DETECT_ONLY=1 to skip the six MUTATING sweeps
|
|
97
|
+
# (backlog_record_reconcile, secondmate_sync,
|
|
98
|
+
# secondmate_liveness_sweep, secondmate_handoff_resume, x_mode_setup,
|
|
99
|
+
# fleet_sync) while still
|
|
100
|
+
# printing every read-only detect line
|
|
101
|
+
# above; the TANGLE line switches to advisory-only wording with no
|
|
102
|
+
# checkout command. Used by
|
|
103
|
+
# fm-session-start.sh's read-only path when another live session holds
|
|
104
|
+
# the fleet lock, so a second concurrent session never race-mutates
|
|
105
|
+
# secondmate homes, pending handoff outboxes,
|
|
106
|
+
# X-mode artifacts, project clones, or repair instructions.
|
|
107
|
+
# Unset/0 (the default) runs all six sweeps - this flag is purely
|
|
108
|
+
# additive.
|
|
109
|
+
# Set FM_BOOTSTRAP_NETWORK to split this run by whether a step talks to
|
|
110
|
+
# the network, so a session start can print its digest from local reads
|
|
111
|
+
# alone and run the network half off the digest's blocking path:
|
|
112
|
+
# all (default, and any unrecognized value) - every local and network
|
|
113
|
+
# step. Unrecognized values fall back here on purpose: a typo
|
|
114
|
+
# must never silently skip a safety sweep.
|
|
115
|
+
# skip - every LOCAL step, and none of the network ones. Skips
|
|
116
|
+
# `gh auth status`, secondmate_liveness_sweep, secondmate_sync,
|
|
117
|
+
# secondmate_handoff_resume, and fleet_sync.
|
|
118
|
+
# only - ONLY those network steps and nothing else. No tool detection,
|
|
119
|
+
# no version floors, no tangle check, no backlog
|
|
120
|
+
# reconciliation, no x_mode_setup: those already ran on the
|
|
121
|
+
# local pass.
|
|
122
|
+
# FM_BOOTSTRAP_DETECT_ONLY composes with it unchanged, so `only` plus
|
|
123
|
+
# detect-only is the read-only `gh auth status` probe on its own.
|
|
124
|
+
# bin/fm-startup-network.sh owns the deferral: it runs the `only` phase
|
|
125
|
+
# in a detached bounded worker and publishes the result. This file stays
|
|
126
|
+
# the single owner of every sweep, and the split changes only WHEN each
|
|
127
|
+
# runs, never WHETHER. During the network phase, project clone refresh
|
|
128
|
+
# overlaps the independent secondmate work. Per-secondmate remote
|
|
129
|
+
# liveness workers run concurrently and finish before per-secondmate
|
|
130
|
+
# remote convergence workers run concurrently, because convergence
|
|
131
|
+
# consumes respawned ids. Worker output is captured separately and
|
|
132
|
+
# replayed in spawn order; failure to create that private capture
|
|
133
|
+
# directory selects the sequential fallback.
|
|
134
|
+
# A relaunch that the liveness sweep performs during an `only` run is
|
|
135
|
+
# always reported, because a digest composed before that run already
|
|
136
|
+
# printed the superseded endpoint record.
|
|
137
|
+
# Set FM_BOOTSTRAP_LOCKED=1 alongside it when the sweeps are skipped
|
|
138
|
+
# because THIS session already ran them while holding the fleet lock,
|
|
139
|
+
# rather than because it has no lock at all. The two cases differ in
|
|
140
|
+
# exactly one place: repair ownership. A locked session is told to
|
|
141
|
+
# restore a tangled primary checkout itself, while an unlocked one is
|
|
142
|
+
# told to leave that work to the lock holder. Unset/0 (the default)
|
|
143
|
+
# keeps detect-only meaning unlocked, exactly as before.
|
|
144
|
+
# fm-bootstrap.sh install <tool>...
|
|
145
|
+
# Install the named tools (only ones the captain approved).
|
|
146
|
+
set -u
|
|
147
|
+
|
|
148
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
149
|
+
FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
150
|
+
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
|
|
151
|
+
PROJECTS="${FM_PROJECTS_OVERRIDE:-$FM_HOME/projects}"
|
|
152
|
+
CONFIG="${FM_CONFIG_OVERRIDE:-$FM_HOME/config}"
|
|
153
|
+
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
|
|
154
|
+
DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
|
|
155
|
+
# shellcheck source=bin/fm-tasks-axi-lib.sh disable=SC1091
|
|
156
|
+
. "$SCRIPT_DIR/fm-tasks-axi-lib.sh"
|
|
157
|
+
# shellcheck source=bin/fm-backlog-transition-lib.sh disable=SC1091
|
|
158
|
+
. "$SCRIPT_DIR/fm-backlog-transition-lib.sh"
|
|
159
|
+
# shellcheck source=bin/fm-quota-axi-lib.sh disable=SC1091
|
|
160
|
+
. "$SCRIPT_DIR/fm-quota-axi-lib.sh"
|
|
161
|
+
# shellcheck source=bin/fm-tangle-lib.sh disable=SC1091
|
|
162
|
+
. "$SCRIPT_DIR/fm-tangle-lib.sh"
|
|
163
|
+
# shellcheck source=bin/fm-ff-lib.sh disable=SC1091
|
|
164
|
+
. "$SCRIPT_DIR/fm-ff-lib.sh"
|
|
165
|
+
# shellcheck source=bin/fm-cursor-lib.sh disable=SC1091
|
|
166
|
+
. "$SCRIPT_DIR/fm-cursor-lib.sh"
|
|
167
|
+
# shellcheck source=bin/fm-config-inherit-lib.sh disable=SC1091
|
|
168
|
+
. "$SCRIPT_DIR/fm-config-inherit-lib.sh"
|
|
169
|
+
# shellcheck source=bin/fm-secondmate-nudge-lib.sh disable=SC1091
|
|
170
|
+
. "$SCRIPT_DIR/fm-secondmate-nudge-lib.sh"
|
|
171
|
+
# shellcheck source=bin/fm-startup-memory-budget-lib.sh disable=SC1091
|
|
172
|
+
. "$SCRIPT_DIR/fm-startup-memory-budget-lib.sh"
|
|
173
|
+
# shellcheck source=bin/fm-x-lib.sh disable=SC1091
|
|
174
|
+
. "$SCRIPT_DIR/fm-x-lib.sh"
|
|
175
|
+
# shellcheck source=bin/fm-backend.sh disable=SC1091
|
|
176
|
+
. "$SCRIPT_DIR/fm-backend.sh"
|
|
177
|
+
# shellcheck source=bin/fm-remote-readiness-lib.sh disable=SC1091
|
|
178
|
+
. "$SCRIPT_DIR/fm-remote-readiness-lib.sh"
|
|
179
|
+
# fm-timing-lib.sh is inert unless FM_TIMING_LOG names a file, which only the
|
|
180
|
+
# deferred network stage sets, so an ordinary bootstrap run records nothing.
|
|
181
|
+
# shellcheck source=bin/fm-timing-lib.sh disable=SC1091
|
|
182
|
+
. "$SCRIPT_DIR/fm-timing-lib.sh"
|
|
183
|
+
|
|
184
|
+
# Network-phase selection (see the header). An unrecognized value resolves to
|
|
185
|
+
# `all` so a malformed override runs every step rather than silently dropping a
|
|
186
|
+
# safety sweep.
|
|
187
|
+
case "${FM_BOOTSTRAP_NETWORK:-all}" in
|
|
188
|
+
skip|only) FM_BOOTSTRAP_NETWORK_PHASE=${FM_BOOTSTRAP_NETWORK:-all} ;;
|
|
189
|
+
*) FM_BOOTSTRAP_NETWORK_PHASE=all ;;
|
|
190
|
+
esac
|
|
191
|
+
local_phase() { [ "$FM_BOOTSTRAP_NETWORK_PHASE" != only ]; }
|
|
192
|
+
network_phase() { [ "$FM_BOOTSTRAP_NETWORK_PHASE" != skip ]; }
|
|
193
|
+
|
|
194
|
+
network_mutation_authorized() {
|
|
195
|
+
local expected=${FM_BOOTSTRAP_NETWORK_LOCK_PID:-} current
|
|
196
|
+
[ -n "$expected" ] || return 0
|
|
197
|
+
case "$expected" in *[!0-9]*) return 1 ;; esac
|
|
198
|
+
[ -f "$STATE/.lock" ] && [ ! -L "$STATE/.lock" ] || return 1
|
|
199
|
+
current=$(cat "$STATE/.lock" 2>/dev/null) || return 1
|
|
200
|
+
[ "$current" = "$expected" ]
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
network_sweep_authorized() {
|
|
204
|
+
local label=$1
|
|
205
|
+
if network_mutation_authorized; then
|
|
206
|
+
return 0
|
|
207
|
+
fi
|
|
208
|
+
echo "NETWORK_CHECKS: fleet lock ownership changed before $label, so this stale worker skipped that sweep"
|
|
209
|
+
return 1
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# Concurrent per-item runner for the deferred network sweeps. Each worker's
|
|
213
|
+
# stdout and stderr are captured to private files and replayed in original
|
|
214
|
+
# order after every worker finishes, so concurrent probes cannot interleave
|
|
215
|
+
# or mis-attribute SECONDMATE_LIVENESS / SECONDMATE_SYNC lines. Respawned ids
|
|
216
|
+
# are collected from per-id files because background workers cannot mutate
|
|
217
|
+
# the parent's SECONDMATE_RESPAWNED_IDS.
|
|
218
|
+
bootstrap_parallel_begin() {
|
|
219
|
+
BOOTSTRAP_PAR_DIR=$(mktemp -d "${TMPDIR:-/tmp}/fm-bootstrap-par.XXXXXX") || return 1
|
|
220
|
+
BOOTSTRAP_PAR_N=0
|
|
221
|
+
FM_BOOTSTRAP_PARALLEL_DIR=$BOOTSTRAP_PAR_DIR
|
|
222
|
+
export FM_BOOTSTRAP_PARALLEL_DIR
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
bootstrap_parallel_spawn() {
|
|
226
|
+
BOOTSTRAP_PAR_N=$((BOOTSTRAP_PAR_N + 1))
|
|
227
|
+
(
|
|
228
|
+
"$@"
|
|
229
|
+
) >"$BOOTSTRAP_PAR_DIR/$BOOTSTRAP_PAR_N.out" 2>"$BOOTSTRAP_PAR_DIR/$BOOTSTRAP_PAR_N.err" &
|
|
230
|
+
printf '%s\n' "$!" > "$BOOTSTRAP_PAR_DIR/$BOOTSTRAP_PAR_N.pid"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
bootstrap_parallel_finish() {
|
|
234
|
+
local i pid f
|
|
235
|
+
i=1
|
|
236
|
+
while [ "$i" -le "$BOOTSTRAP_PAR_N" ]; do
|
|
237
|
+
pid=$(cat "$BOOTSTRAP_PAR_DIR/$i.pid")
|
|
238
|
+
wait "$pid" || true
|
|
239
|
+
i=$((i + 1))
|
|
240
|
+
done
|
|
241
|
+
i=1
|
|
242
|
+
while [ "$i" -le "$BOOTSTRAP_PAR_N" ]; do
|
|
243
|
+
cat "$BOOTSTRAP_PAR_DIR/$i.out"
|
|
244
|
+
cat "$BOOTSTRAP_PAR_DIR/$i.err" >&2
|
|
245
|
+
i=$((i + 1))
|
|
246
|
+
done
|
|
247
|
+
for f in "$BOOTSTRAP_PAR_DIR"/respawned.*; do
|
|
248
|
+
[ -f "$f" ] || continue
|
|
249
|
+
SECONDMATE_RESPAWNED_IDS="$SECONDMATE_RESPAWNED_IDS $(tr -d '\n' < "$f")"
|
|
250
|
+
done
|
|
251
|
+
rm -rf "$BOOTSTRAP_PAR_DIR"
|
|
252
|
+
unset FM_BOOTSTRAP_PARALLEL_DIR BOOTSTRAP_PAR_DIR BOOTSTRAP_PAR_N
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
secondmate_note_respawned() { # <id>
|
|
256
|
+
SECONDMATE_RESPAWNED_IDS="$SECONDMATE_RESPAWNED_IDS $1"
|
|
257
|
+
[ -n "${FM_BOOTSTRAP_PARALLEL_DIR:-}" ] || return 0
|
|
258
|
+
printf '%s\n' "$1" > "$FM_BOOTSTRAP_PARALLEL_DIR/respawned.$1"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fleet_sync_origin_backed_project_count() {
|
|
262
|
+
local count proj
|
|
263
|
+
count=0
|
|
264
|
+
[ -d "$PROJECTS" ] || { echo 0; return 0; }
|
|
265
|
+
for proj in "$PROJECTS"/*; do
|
|
266
|
+
[ -d "$proj" ] || continue
|
|
267
|
+
git -C "$proj" rev-parse --git-dir >/dev/null 2>&1 || continue
|
|
268
|
+
git -C "$proj" remote get-url origin >/dev/null 2>&1 || continue
|
|
269
|
+
count=$((count + 1))
|
|
270
|
+
done
|
|
271
|
+
echo "$count"
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
fleet_sync_bootstrap_timeout() {
|
|
275
|
+
local count timeout
|
|
276
|
+
if [ -n "${FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT:-}" ]; then
|
|
277
|
+
case "$FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT" in
|
|
278
|
+
*[!0-9]*) echo 20 ;;
|
|
279
|
+
*) echo "$FM_FLEET_SYNC_BOOTSTRAP_TIMEOUT" ;;
|
|
280
|
+
esac
|
|
281
|
+
return 0
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
count=$(fleet_sync_origin_backed_project_count)
|
|
285
|
+
timeout=$((5 + (3 * count)))
|
|
286
|
+
[ "$timeout" -ge 20 ] || timeout=20
|
|
287
|
+
echo "$timeout"
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
fleet_sync_relay_filtered_output() {
|
|
291
|
+
local tmp=$1 line
|
|
292
|
+
while IFS= read -r line; do
|
|
293
|
+
case "$line" in
|
|
294
|
+
*': skipped: local-only project') ;;
|
|
295
|
+
*': skipped: no origin remote') ;;
|
|
296
|
+
*': skipped:'*) echo "FLEET_SYNC: $line" ;;
|
|
297
|
+
*': STUCK:'*) echo "FLEET_SYNC: $line" ;;
|
|
298
|
+
*': recovered:'*) echo "FLEET_SYNC: $line" ;;
|
|
299
|
+
esac
|
|
300
|
+
done < "$tmp"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
fleet_sync_relay_all_output() {
|
|
304
|
+
local tmp=$1 line
|
|
305
|
+
while IFS= read -r line; do
|
|
306
|
+
[ -n "$line" ] || continue
|
|
307
|
+
echo "FLEET_SYNC: $line"
|
|
308
|
+
done < "$tmp"
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
fleet_sync() {
|
|
312
|
+
[ -x "$FM_ROOT/bin/fm-fleet-sync.sh" ] || return 0
|
|
313
|
+
[ -d "$PROJECTS" ] || return 0
|
|
314
|
+
|
|
315
|
+
tmp=$(mktemp "${TMPDIR:-/tmp}/fm-fleet-sync.XXXXXX" 2>/dev/null) || return 0
|
|
316
|
+
timeout=$(fleet_sync_bootstrap_timeout)
|
|
317
|
+
monitor_was_on=0
|
|
318
|
+
case $- in *m*) monitor_was_on=1 ;; esac
|
|
319
|
+
set -m 2>/dev/null || true
|
|
320
|
+
"$FM_ROOT/bin/fm-fleet-sync.sh" >"$tmp" 2>/dev/null &
|
|
321
|
+
pid=$!
|
|
322
|
+
|
|
323
|
+
start=$SECONDS
|
|
324
|
+
while jobs -r -p | grep -qx "$pid"; do
|
|
325
|
+
elapsed=$((SECONDS - start))
|
|
326
|
+
if [ "$elapsed" -ge "$timeout" ]; then
|
|
327
|
+
kill -TERM "-$pid" 2>/dev/null || kill "$pid" 2>/dev/null || true
|
|
328
|
+
wait "$pid" 2>/dev/null || true
|
|
329
|
+
[ "$monitor_was_on" -eq 1 ] || set +m 2>/dev/null || true
|
|
330
|
+
fleet_sync_relay_all_output "$tmp"
|
|
331
|
+
echo "FLEET_SYNC: fleet: skipped: bootstrap refresh timed out (timeout=${timeout}s elapsed=${elapsed}s)"
|
|
332
|
+
rm -f "$tmp"
|
|
333
|
+
return 0
|
|
334
|
+
fi
|
|
335
|
+
sleep 1
|
|
336
|
+
done
|
|
337
|
+
wait "$pid" 2>/dev/null || true
|
|
338
|
+
[ "$monitor_was_on" -eq 1 ] || set +m 2>/dev/null || true
|
|
339
|
+
|
|
340
|
+
fleet_sync_relay_filtered_output "$tmp"
|
|
341
|
+
rm -f "$tmp"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
secondmate_sync() {
|
|
345
|
+
# shellcheck source=bin/fm-wake-lib.sh disable=SC1091
|
|
346
|
+
. "$SCRIPT_DIR/fm-wake-lib.sh"
|
|
347
|
+
# Placement-specific secondmate sync: local homes fast-forward to the primary
|
|
348
|
+
# checkout's current default-branch commit. That path is purely LOCAL - no
|
|
349
|
+
# fetch, no origin dependency: a linked-worktree home already holds the primary's
|
|
350
|
+
# commit (fm-ff-lib.sh), while a standalone clone without it is skipped until
|
|
351
|
+
# /updatefirstmate refreshes it from origin. Startup sends reread nudges only
|
|
352
|
+
# for RUNNING secondmates whose instruction surface (AGENTS.md, bin/, or
|
|
353
|
+
# .agents/skills/) actually changed, so a secondmate already on the primary's
|
|
354
|
+
# version is never disturbed (AGENTS.md bootstrap + supervision). Unlike
|
|
355
|
+
# /updatefirstmate, startup owns the live-convergence send itself because it is
|
|
356
|
+
# a deterministic locked sweep and can report success as BOOTSTRAP_INFO while
|
|
357
|
+
# preserving failed sends as NUDGE_SECONDMATES retry markers.
|
|
358
|
+
[ -d "$STATE" ] || return 0
|
|
359
|
+
local primary_head
|
|
360
|
+
if ! primary_head=$(primary_head_commit "$FM_ROOT"); then
|
|
361
|
+
local meta id
|
|
362
|
+
for meta in "$STATE"/*.meta; do
|
|
363
|
+
[ -f "$meta" ] || continue
|
|
364
|
+
grep -q '^kind=secondmate' "$meta" 2>/dev/null || continue
|
|
365
|
+
id=$(basename "$meta" .meta)
|
|
366
|
+
echo "SECONDMATE_SYNC: secondmate $id: skipped: primary default-branch commit cannot be resolved"
|
|
367
|
+
done
|
|
368
|
+
return 0
|
|
369
|
+
fi
|
|
370
|
+
FF_NUDGE_WINDOWS=""
|
|
371
|
+
FF_SEEN_HOMES=""
|
|
372
|
+
SECOND_MATE_NUDGE_MESSAGE=$FM_SECOND_MATE_NUDGE_MESSAGE
|
|
373
|
+
REMOTE_SECOND_MATE_NUDGE_MESSAGE=$FM_REMOTE_SECOND_MATE_NUDGE_MESSAGE
|
|
374
|
+
SECOND_MATE_NUDGE_PENDING_DIR="$STATE/.secondmate-nudge-pending"
|
|
375
|
+
|
|
376
|
+
secondmate_nudge_marker_path() {
|
|
377
|
+
fm_secondmate_nudge_marker_path "$STATE" "$1"
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
secondmate_write_nudge_marker() {
|
|
381
|
+
local id=$1 home=$2 commit=$3 instr=$4 message=${5:-$SECOND_MATE_NUDGE_MESSAGE} remote=${6:-0}
|
|
382
|
+
fm_secondmate_nudge_write "$STATE" "$id" "$home" "$commit" "$instr" "$message" "$remote"
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
secondmate_send_nudge() {
|
|
386
|
+
local id=$1 home=$2 commit=$3 instr=$4 selector marker out
|
|
387
|
+
selector="fm-$id"
|
|
388
|
+
marker=$(secondmate_nudge_marker_path "$id") || {
|
|
389
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: unsafe id"
|
|
390
|
+
return 0
|
|
391
|
+
}
|
|
392
|
+
if ! secondmate_write_nudge_marker "$id" "$home" "$commit" "$instr"; then
|
|
393
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: cannot record retry marker"
|
|
394
|
+
return 0
|
|
395
|
+
fi
|
|
396
|
+
if out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" FM_STATE_OVERRIDE="$STATE" "$SCRIPT_DIR/fm-send.sh" "$selector" "$SECOND_MATE_NUDGE_MESSAGE" 2>&1); then
|
|
397
|
+
rm -f "$marker"
|
|
398
|
+
echo "BOOTSTRAP_INFO: nudged $selector with '$SECOND_MATE_NUDGE_MESSAGE'"
|
|
399
|
+
else
|
|
400
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: $(first_line "$out")"
|
|
401
|
+
fi
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
fm_ff_after_instruction_update() {
|
|
405
|
+
local id=$1 home=$2 _window=$3 instr=$4
|
|
406
|
+
secondmate_send_nudge "$id" "$home" "$primary_head" "$instr"
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
secondmate_retry_pending_nudges() {
|
|
410
|
+
local marker id selector home commit message remote expected_marker meta meta_home home_real head out
|
|
411
|
+
[ -d "$SECOND_MATE_NUDGE_PENDING_DIR" ] || return 0
|
|
412
|
+
for marker in "$SECOND_MATE_NUDGE_PENDING_DIR"/*.pending; do
|
|
413
|
+
[ -f "$marker" ] || continue
|
|
414
|
+
id=$(fm_meta_get "$marker" id)
|
|
415
|
+
if ! expected_marker=$(secondmate_nudge_marker_path "$id"); then
|
|
416
|
+
echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker has unsafe id"
|
|
417
|
+
continue
|
|
418
|
+
fi
|
|
419
|
+
[ "$expected_marker" = "$marker" ] || {
|
|
420
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry marker filename mismatch"
|
|
421
|
+
continue
|
|
422
|
+
}
|
|
423
|
+
selector=$(fm_meta_get "$marker" selector)
|
|
424
|
+
home=$(fm_meta_get "$marker" home)
|
|
425
|
+
commit=$(fm_meta_get "$marker" commit)
|
|
426
|
+
message=$(fm_meta_get "$marker" message)
|
|
427
|
+
remote=$(fm_meta_get "$marker" remote)
|
|
428
|
+
[ -n "$remote" ] || remote=0
|
|
429
|
+
[ "$selector" = "fm-$id" ] || {
|
|
430
|
+
echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker selector mismatch"
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
433
|
+
case "$remote" in
|
|
434
|
+
0) [ "$message" = "$SECOND_MATE_NUDGE_MESSAGE" ] || {
|
|
435
|
+
echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker message mismatch"
|
|
436
|
+
continue
|
|
437
|
+
} ;;
|
|
438
|
+
1) [ "$message" = "$REMOTE_SECOND_MATE_NUDGE_MESSAGE" ] || {
|
|
439
|
+
echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: remote retry marker message mismatch"
|
|
440
|
+
continue
|
|
441
|
+
} ;;
|
|
442
|
+
*)
|
|
443
|
+
echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry marker placement is invalid"
|
|
444
|
+
continue
|
|
445
|
+
;;
|
|
446
|
+
esac
|
|
447
|
+
[ "$remote" -ne 1 ] || continue
|
|
448
|
+
meta="$STATE/$id.meta"
|
|
449
|
+
[ -f "$meta" ] && [ "$(fm_meta_get "$meta" kind)" = secondmate ] || {
|
|
450
|
+
echo "NUDGE_SECONDMATES: secondmate ${id:-unknown}: send failed: retry target has no live secondmate metadata"
|
|
451
|
+
continue
|
|
452
|
+
}
|
|
453
|
+
meta_home=$(fm_meta_get "$meta" home)
|
|
454
|
+
[ -n "$meta_home" ] || meta_home=$(secondmate_registry_field "$DATA/secondmates.md" "$id" home || true)
|
|
455
|
+
if ! validate_secondmate_home "$id" "$meta_home"; then
|
|
456
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry target home unsafe: $VALIDATION_ERROR"
|
|
457
|
+
continue
|
|
458
|
+
fi
|
|
459
|
+
home_real="$VALIDATED_HOME"
|
|
460
|
+
[ "$home_real" = "$home" ] || {
|
|
461
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry target home changed"
|
|
462
|
+
continue
|
|
463
|
+
}
|
|
464
|
+
head=$(git -C "$home_real" rev-parse HEAD 2>/dev/null || true)
|
|
465
|
+
[ -n "$head" ] && [ "$head" = "$commit" ] || {
|
|
466
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: retry target is not at recorded instruction commit"
|
|
467
|
+
continue
|
|
468
|
+
}
|
|
469
|
+
if out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" FM_STATE_OVERRIDE="$STATE" "$SCRIPT_DIR/fm-send.sh" "$selector" "$SECOND_MATE_NUDGE_MESSAGE" 2>&1); then
|
|
470
|
+
rm -f "$marker"
|
|
471
|
+
echo "BOOTSTRAP_INFO: nudged $selector with '$SECOND_MATE_NUDGE_MESSAGE'"
|
|
472
|
+
else
|
|
473
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: $(first_line "$out")"
|
|
474
|
+
fi
|
|
475
|
+
done
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
local tmp line
|
|
479
|
+
secondmate_retry_pending_nudges
|
|
480
|
+
tmp=$(mktemp "${TMPDIR:-/tmp}/fm-secondmate-sync.XXXXXX" 2>/dev/null) || return 0
|
|
481
|
+
sweep_live_secondmate_metas "$STATE" "$primary_head" yes "$DATA/secondmates.md" >"$tmp"
|
|
482
|
+
while IFS= read -r line; do
|
|
483
|
+
case "$line" in
|
|
484
|
+
secondmate\ *': skipped:'*) echo "SECONDMATE_SYNC: $line" ;;
|
|
485
|
+
BOOTSTRAP_INFO:\ *) echo "$line" ;;
|
|
486
|
+
NUDGE_SECONDMATES:\ *) echo "$line" ;;
|
|
487
|
+
esac
|
|
488
|
+
done < "$tmp"
|
|
489
|
+
rm -f "$tmp"
|
|
490
|
+
unset -f fm_ff_after_instruction_update
|
|
491
|
+
# Inheritance propagation: push the primary-authoritative local inheritance
|
|
492
|
+
# surface into every VALIDATED live secondmate home swept above.
|
|
493
|
+
# FF_SEEN_HOMES is exactly that set, and fm-config-inherit-lib.sh owns the
|
|
494
|
+
# declared config items plus data/captain-shared.md.
|
|
495
|
+
# After a successful push that changes allowlisted config/* for an already-
|
|
496
|
+
# running home, send its literal-content reread instruction pointer so the
|
|
497
|
+
# live agent does not keep applying stale defaults. Spawn/respawn already
|
|
498
|
+
# re-reads at launch and needs no redundant nudge unless files changed after launch.
|
|
499
|
+
local id home home_real home_lock propagated_homes report reread_out reread_skip_pending
|
|
500
|
+
propagated_homes=""
|
|
501
|
+
SECONDMATE_RESPAWNED_IDS=${SECONDMATE_RESPAWNED_IDS:-}
|
|
502
|
+
while IFS='|' read -r id home _window _meta; do
|
|
503
|
+
validate_secondmate_home "$id" "$home" || continue
|
|
504
|
+
home_real="$VALIDATED_HOME"
|
|
505
|
+
case " $FF_SEEN_HOMES " in
|
|
506
|
+
*" $home_real "*) ;;
|
|
507
|
+
*) continue ;;
|
|
508
|
+
esac
|
|
509
|
+
case " $propagated_homes " in
|
|
510
|
+
*" $home_real "*) continue ;;
|
|
511
|
+
esac
|
|
512
|
+
propagated_homes="$propagated_homes $home_real"
|
|
513
|
+
mkdir -p "$home_real/state" || {
|
|
514
|
+
echo "CONFIG_REREAD: secondmate $id: send failed: could not create state directory"
|
|
515
|
+
continue
|
|
516
|
+
}
|
|
517
|
+
home_lock=$(fm_config_inherit_lock_path "$home_real") || {
|
|
518
|
+
echo "CONFIG_REREAD: secondmate $id: send failed: could not resolve per-home lock"
|
|
519
|
+
continue
|
|
520
|
+
}
|
|
521
|
+
fm_lock_acquire_wait "$home_lock" || {
|
|
522
|
+
echo "CONFIG_REREAD: secondmate $id: send failed: could not acquire per-home lock"
|
|
523
|
+
continue
|
|
524
|
+
}
|
|
525
|
+
reread_skip_pending=0
|
|
526
|
+
case " $SECONDMATE_RESPAWNED_IDS " in
|
|
527
|
+
*" $id "*) reread_skip_pending=1 ;;
|
|
528
|
+
esac
|
|
529
|
+
if [ "$reread_skip_pending" -eq 0 ] \
|
|
530
|
+
&& fm_config_reread_retry_queue_is_full "$FM_HOME" "$id"; then
|
|
531
|
+
fm_config_reread_retry_pending "$id" "$home_real" || true
|
|
532
|
+
if fm_config_reread_retry_queue_is_full "$FM_HOME" "$id"; then
|
|
533
|
+
echo "CONFIG_REREAD: secondmate $id: send failed: retry instruction queue is full"
|
|
534
|
+
fm_lock_release "$home_lock" || true
|
|
535
|
+
continue
|
|
536
|
+
fi
|
|
537
|
+
fi
|
|
538
|
+
report=$(mktemp "${TMPDIR:-/tmp}/fm-bootstrap-inherit.XXXXXX" 2>/dev/null) || {
|
|
539
|
+
echo "SECONDMATE_SYNC: secondmate $id: skipped: inheritance failed"
|
|
540
|
+
fm_lock_release "$home_lock" || true
|
|
541
|
+
continue
|
|
542
|
+
}
|
|
543
|
+
if FM_CONFIG_INHERIT_REPORT="$report" FM_CONFIG_INHERIT_LIVE=1 \
|
|
544
|
+
propagate_secondmate_inheritance "$FM_HOME" "$home_real" "$CONFIG" "$DATA"; then
|
|
545
|
+
:
|
|
546
|
+
else
|
|
547
|
+
echo "SECONDMATE_SYNC: secondmate $id: skipped: inheritance failed"
|
|
548
|
+
fi
|
|
549
|
+
if ! reread_out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" \
|
|
550
|
+
FM_STATE_OVERRIDE="$STATE" \
|
|
551
|
+
FM_CONFIG_REREAD_SKIP_PENDING="$reread_skip_pending" \
|
|
552
|
+
fm_config_send_reread_nudge "$id" "$home_real" "$report" 2>&1); then
|
|
553
|
+
if [ -n "$reread_out" ]; then
|
|
554
|
+
printf '%s\n' "$reread_out"
|
|
555
|
+
else
|
|
556
|
+
echo "CONFIG_REREAD: secondmate $id: send failed: unknown error"
|
|
557
|
+
fi
|
|
558
|
+
elif [ -n "$reread_out" ]; then
|
|
559
|
+
printf '%s\n' "$reread_out"
|
|
560
|
+
fi
|
|
561
|
+
rm -f "$report"
|
|
562
|
+
fm_lock_release "$home_lock" || true
|
|
563
|
+
done < <(live_secondmate_meta_records "$STATE" "$DATA/secondmates.md")
|
|
564
|
+
|
|
565
|
+
# One remote secondmate's convergence, split out of the loop so each host is
|
|
566
|
+
# individually timed; every `return` here was a `continue` and still means
|
|
567
|
+
# "move on to the next secondmate".
|
|
568
|
+
secondmate_sync_remote_one() { # <id> <home> <remote-host>
|
|
569
|
+
local id=$1 _home=$2 remote_host=$3
|
|
570
|
+
local sync_out inherit_out nudge_needed remote_marker remote_pending converged out remote_lock remote_generation
|
|
571
|
+
remote_lock=$(fm_remote_inherit_transaction_lock_path "$STATE" "$id" 2>/dev/null || true)
|
|
572
|
+
if [ -z "$remote_lock" ] || ! fm_lock_acquire_wait "$remote_lock"; then
|
|
573
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: cannot lock remote inheritance transaction"
|
|
574
|
+
return 0
|
|
575
|
+
fi
|
|
576
|
+
if ! "$SCRIPT_DIR/fm-procevent-remote-reply.sh" arm "$id" >/dev/null 2>&1; then
|
|
577
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote reply source could not be registered"
|
|
578
|
+
fi
|
|
579
|
+
remote_generation=$(fm_remote_inherit_generation_next "$STATE" "$id" 2>/dev/null || true)
|
|
580
|
+
if [ -z "$remote_generation" ]; then
|
|
581
|
+
echo "SECONDMATE_SYNC: secondmate $id: skipped: remote inheritance generation could not be published"
|
|
582
|
+
fm_lock_release "$remote_lock" || true
|
|
583
|
+
return 0
|
|
584
|
+
fi
|
|
585
|
+
remote_marker=$(secondmate_nudge_marker_path "$id" 2>/dev/null || true)
|
|
586
|
+
remote_pending=0
|
|
587
|
+
if [ -f "$remote_marker" ] && [ "$(fm_meta_get "$remote_marker" remote)" = 1 ]; then remote_pending=1; fi
|
|
588
|
+
if ! secondmate_write_nudge_marker "$id" "$_home" "" remote \
|
|
589
|
+
"$REMOTE_SECOND_MATE_NUDGE_MESSAGE" 1; then
|
|
590
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: cannot record remote retry marker"
|
|
591
|
+
fm_lock_release "$remote_lock" || true
|
|
592
|
+
return 0
|
|
593
|
+
fi
|
|
594
|
+
nudge_needed=0
|
|
595
|
+
converged=1
|
|
596
|
+
if sync_out=$("$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh sync "$id" < /dev/null 2>&1); then
|
|
597
|
+
case "$sync_out" in synced:*) nudge_needed=1 ;; esac
|
|
598
|
+
else
|
|
599
|
+
echo "SECONDMATE_SYNC: secondmate $id: skipped: remote tracked-file sync failed on $remote_host: $(first_line "$sync_out")"
|
|
600
|
+
converged=0
|
|
601
|
+
fi
|
|
602
|
+
if inherit_out=$(FM_CONFIG_INHERIT_LIVE=1 \
|
|
603
|
+
"$SCRIPT_DIR/fm-remote-inherit-push.sh" "$id" "$remote_generation" 2>&1); then
|
|
604
|
+
if printf '%s\n' "$inherit_out" | grep -Eq '^(pushed|removed):'; then nudge_needed=1; fi
|
|
605
|
+
else
|
|
606
|
+
echo "SECONDMATE_SYNC: secondmate $id: skipped: remote inheritance failed on $remote_host: $(first_line "$inherit_out")"
|
|
607
|
+
converged=0
|
|
608
|
+
fi
|
|
609
|
+
[ "$remote_pending" -eq 0 ] || nudge_needed=1
|
|
610
|
+
if [ "$converged" -eq 1 ] && [ "$nudge_needed" -eq 1 ]; then
|
|
611
|
+
if out=$(FM_HOME="$FM_HOME" FM_ROOT_OVERRIDE="$FM_ROOT" FM_STATE_OVERRIDE="$STATE" \
|
|
612
|
+
"$SCRIPT_DIR/fm-send.sh" "fm-$id" "$REMOTE_SECOND_MATE_NUDGE_MESSAGE" 2>&1); then
|
|
613
|
+
rm -f "$remote_marker"
|
|
614
|
+
[ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" != 1 ] || echo "BOOTSTRAP_INFO: nudged remote fm-$id after convergence"
|
|
615
|
+
else
|
|
616
|
+
echo "NUDGE_SECONDMATES: secondmate $id: send failed: $(first_line "$out")"
|
|
617
|
+
fi
|
|
618
|
+
elif [ "$converged" -eq 1 ]; then
|
|
619
|
+
rm -f "$remote_marker"
|
|
620
|
+
fi
|
|
621
|
+
fm_lock_release "$remote_lock" || true
|
|
622
|
+
return 0
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
secondmate_sync_remote_one_timed() { # <id> <home> <remote-host>
|
|
626
|
+
local id=$1 home=$2 remote_host=$3 __fm_timing_stamp
|
|
627
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
628
|
+
secondmate_sync_remote_one "$id" "$home" "$remote_host"
|
|
629
|
+
fm_timing_record secondmate convergence "$__fm_timing_stamp" "$id@$remote_host"
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
# Remote routes converge through the generic transport. Their code root and
|
|
633
|
+
# inherited files are authoritative on that host; no local path probe or
|
|
634
|
+
# local fast-forward is attempted for them.
|
|
635
|
+
local remote_host __fm_timing_stamp parallel=0
|
|
636
|
+
if bootstrap_parallel_begin; then
|
|
637
|
+
parallel=1
|
|
638
|
+
fi
|
|
639
|
+
while IFS='|' read -r id _home _window meta; do
|
|
640
|
+
remote_host=$(fm_meta_get "$meta" remote_host)
|
|
641
|
+
[ -n "$remote_host" ] || continue
|
|
642
|
+
if [ "$parallel" -eq 1 ]; then
|
|
643
|
+
bootstrap_parallel_spawn secondmate_sync_remote_one_timed "$id" "$_home" "$remote_host"
|
|
644
|
+
else
|
|
645
|
+
secondmate_sync_remote_one_timed "$id" "$_home" "$remote_host"
|
|
646
|
+
fi
|
|
647
|
+
done < <(live_secondmate_meta_records "$STATE" "$DATA/secondmates.md")
|
|
648
|
+
[ "$parallel" -eq 0 ] || bootstrap_parallel_finish
|
|
649
|
+
return 0
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
# A relaunch replaces the endpoint record a digest may already have printed. On
|
|
653
|
+
# the local pass that digest has not been composed yet, so the fact stays behind
|
|
654
|
+
# FM_BOOTSTRAP_VERBOSE_FACTS as before; on the deferred network pass the digest
|
|
655
|
+
# is already out, so reporting it is what keeps the superseded record from being
|
|
656
|
+
# acted on.
|
|
657
|
+
report_relaunch() { # <id> <cause> <where>
|
|
658
|
+
[ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] || ! local_phase || return 0
|
|
659
|
+
echo "BOOTSTRAP_INFO: secondmate $1 relaunched after $2 ($3)"
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
secondmate_liveness_sweep() {
|
|
663
|
+
# Idempotent secondmate liveness guarantee - SESSION START ONLY. The detailed
|
|
664
|
+
# state machine and its only recovery-authorizing states are owned by
|
|
665
|
+
# fm_backend_agent_state. A missing tmux pane is not enough: tmux must prove
|
|
666
|
+
# the window or session absent. This preserves duplicate prevention for
|
|
667
|
+
# existing ambiguous processes and every transiently unreadable target while
|
|
668
|
+
# adding the missing-session path the original bare-shell and Herdr-husk sweep
|
|
669
|
+
# lacked.
|
|
670
|
+
# A meta with no window remains owned by secondmate-provisioning recovery.
|
|
671
|
+
# Secondmate homes never contain kind=secondmate meta, so this is naturally a
|
|
672
|
+
# primary-only no-op there. Mid-session liveness remains explicitly out of
|
|
673
|
+
# scope and requires a separate periodic signal.
|
|
674
|
+
[ -d "$STATE" ] || return 0
|
|
675
|
+
local meta id remote_host label __fm_timing_stamp parallel=0
|
|
676
|
+
SECONDMATE_RESPAWNED_IDS=""
|
|
677
|
+
if bootstrap_parallel_begin; then
|
|
678
|
+
parallel=1
|
|
679
|
+
fi
|
|
680
|
+
for meta in "$STATE"/*.meta; do
|
|
681
|
+
[ -f "$meta" ] || continue
|
|
682
|
+
grep -q '^kind=secondmate$' "$meta" 2>/dev/null || continue
|
|
683
|
+
# Identity for the timing record is read here, in the loop, so the per-meta
|
|
684
|
+
# body below keeps its single-exit-per-outcome shape.
|
|
685
|
+
id=$(basename "$meta" .meta)
|
|
686
|
+
remote_host=$(fm_meta_get "$meta" remote_host)
|
|
687
|
+
label=$id
|
|
688
|
+
[ -z "$remote_host" ] || label="$id@$remote_host"
|
|
689
|
+
if [ "$parallel" -eq 1 ]; then
|
|
690
|
+
bootstrap_parallel_spawn secondmate_liveness_one_timed "$meta" "$id" "$label"
|
|
691
|
+
else
|
|
692
|
+
secondmate_liveness_one_timed "$meta" "$id" "$label"
|
|
693
|
+
fi
|
|
694
|
+
done
|
|
695
|
+
[ "$parallel" -eq 0 ] || bootstrap_parallel_finish
|
|
696
|
+
return 0
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
secondmate_liveness_one_timed() { # <meta> <id> <label>
|
|
700
|
+
local meta=$1 id=$2 label=$3 __fm_timing_stamp
|
|
701
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
702
|
+
secondmate_liveness_one "$meta" "$id"
|
|
703
|
+
fm_timing_record secondmate liveness "$__fm_timing_stamp" "$label"
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
# One secondmate's liveness check. Split out of the sweep so each is individually
|
|
707
|
+
# timed; every `return` here was a `continue` in the loop and means exactly the
|
|
708
|
+
# same thing - move on to the next secondmate. Respawned ids are recorded through
|
|
709
|
+
# secondmate_note_respawned so a concurrent sweep can collect them after wait.
|
|
710
|
+
secondmate_liveness_one() { # <meta> <id>
|
|
711
|
+
local meta=$1 id=$2
|
|
712
|
+
local window harness backend target agent_state out cause remote_host remote_rc readiness_reason route_out remote_backend
|
|
713
|
+
window=$(fm_meta_get "$meta" window)
|
|
714
|
+
[ -n "$window" ] || return 0
|
|
715
|
+
harness=$(fm_meta_get "$meta" harness)
|
|
716
|
+
remote_host=$(fm_meta_get "$meta" remote_host)
|
|
717
|
+
if [ -n "$remote_host" ]; then
|
|
718
|
+
remote_rc=0
|
|
719
|
+
fm_remote_readiness_ensure "$SCRIPT_DIR" "$id" || remote_rc=$?
|
|
720
|
+
if [ "$remote_rc" -eq 255 ]; then
|
|
721
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote host unavailable or endpoint state unknown; route preserved on $remote_host"
|
|
722
|
+
return 0
|
|
723
|
+
fi
|
|
724
|
+
if [ "$remote_rc" -ne 0 ]; then
|
|
725
|
+
readiness_reason=$(printf '%s\n' "$FM_REMOTE_READINESS_OUT" \
|
|
726
|
+
| awk '/^check [^=]+=(fixable|human):|^action:|^error:/ { print; exit }')
|
|
727
|
+
[ -n "$readiness_reason" ] || readiness_reason=$(first_line "$FM_REMOTE_READINESS_OUT")
|
|
728
|
+
[ -n "$readiness_reason" ] || readiness_reason="unknown readiness failure"
|
|
729
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote readiness failed on $remote_host: $readiness_reason"
|
|
730
|
+
return 0
|
|
731
|
+
fi
|
|
732
|
+
if out=$("$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh state "$id" < /dev/null 2>/dev/null); then
|
|
733
|
+
remote_rc=0
|
|
734
|
+
else
|
|
735
|
+
remote_rc=$?
|
|
736
|
+
fi
|
|
737
|
+
if [ "$remote_rc" -eq 255 ]; then
|
|
738
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote host unavailable or endpoint state unknown; route preserved on $remote_host"
|
|
739
|
+
return 0
|
|
740
|
+
fi
|
|
741
|
+
if [ "$remote_rc" -ne 0 ]; then
|
|
742
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote endpoint probe unreadable on $remote_host"
|
|
743
|
+
return 0
|
|
744
|
+
fi
|
|
745
|
+
agent_state=$(printf '%s\n' "$out" | tail -1)
|
|
746
|
+
case "$agent_state" in
|
|
747
|
+
alive)
|
|
748
|
+
if route_out=$("$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh route "$id" < /dev/null 2>/dev/null); then
|
|
749
|
+
remote_rc=0
|
|
750
|
+
else
|
|
751
|
+
remote_rc=$?
|
|
752
|
+
fi
|
|
753
|
+
if [ "$remote_rc" -eq 255 ]; then
|
|
754
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote host unavailable or endpoint route unknown; route preserved on $remote_host"
|
|
755
|
+
return 0
|
|
756
|
+
fi
|
|
757
|
+
if [ "$remote_rc" -ne 0 ]; then
|
|
758
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: alive remote endpoint route is unreadable on $remote_host; inspect and migrate or retire it explicitly"
|
|
759
|
+
return 0
|
|
760
|
+
fi
|
|
761
|
+
remote_backend=$(printf '%s\n' "$route_out" | sed -n 's/^backend=//p' | tail -1)
|
|
762
|
+
if [ "$remote_backend" != herdr ]; then
|
|
763
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: alive remote endpoint is recorded on backend '${remote_backend:-missing}'; migrate or retire it explicitly"
|
|
764
|
+
return 0
|
|
765
|
+
fi
|
|
766
|
+
[ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" != 1 ] || echo "BOOTSTRAP_INFO: remote secondmate $id already live (host=$remote_host)"
|
|
767
|
+
;;
|
|
768
|
+
dead|missing)
|
|
769
|
+
cause="remote endpoint $agent_state on its configured host"
|
|
770
|
+
if out=$(FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "$id" --secondmate 2>&1); then
|
|
771
|
+
secondmate_note_respawned "$id"
|
|
772
|
+
report_relaunch "$id" "$cause" "host=$remote_host"
|
|
773
|
+
else
|
|
774
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: respawn failed after $cause: $(first_line "$out")"
|
|
775
|
+
fi
|
|
776
|
+
;;
|
|
777
|
+
ambiguous|unreadable|unverified)
|
|
778
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote endpoint state is $agent_state on $remote_host"
|
|
779
|
+
;;
|
|
780
|
+
*) echo "SECONDMATE_LIVENESS: secondmate $id: skipped: remote endpoint returned an invalid state" ;;
|
|
781
|
+
esac
|
|
782
|
+
return 0
|
|
783
|
+
fi
|
|
784
|
+
backend=$(fm_backend_of_meta "$meta")
|
|
785
|
+
target=$(fm_backend_target_of_meta "$meta")
|
|
786
|
+
[ -n "$target" ] || target="$window"
|
|
787
|
+
agent_state=$(fm_backend_agent_state "$backend" "$target" 2>/dev/null) || agent_state=unreadable
|
|
788
|
+
case "$harness" in
|
|
789
|
+
claude|codex|opencode|pi|pi-signed|grok|kimi) ;;
|
|
790
|
+
*)
|
|
791
|
+
case "$agent_state" in dead|missing) agent_state=unverified-harness ;; esac
|
|
792
|
+
;;
|
|
793
|
+
esac
|
|
794
|
+
case "$agent_state" in
|
|
795
|
+
alive)
|
|
796
|
+
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ]; then
|
|
797
|
+
echo "BOOTSTRAP_INFO: secondmate $id already live (backend=$backend)"
|
|
798
|
+
fi
|
|
799
|
+
;;
|
|
800
|
+
dead|missing)
|
|
801
|
+
if [ "$agent_state" = dead ]; then
|
|
802
|
+
cause="confirmed agent absence on existing endpoint"
|
|
803
|
+
fm_backend_kill "$backend" "$target" 2>/dev/null || true
|
|
804
|
+
else
|
|
805
|
+
cause="recorded endpoint confidently missing"
|
|
806
|
+
fi
|
|
807
|
+
if out=$(FM_SPAWN_NO_GUARD=1 "$FM_ROOT/bin/fm-spawn.sh" "$id" --secondmate 2>&1); then
|
|
808
|
+
secondmate_note_respawned "$id"
|
|
809
|
+
report_relaunch "$id" "$cause" "backend=$backend"
|
|
810
|
+
else
|
|
811
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: respawn failed after $cause: $(first_line "$out")"
|
|
812
|
+
fi
|
|
813
|
+
;;
|
|
814
|
+
ambiguous)
|
|
815
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: existing endpoint has ambiguous agent process (backend=$backend)"
|
|
816
|
+
;;
|
|
817
|
+
unreadable)
|
|
818
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: endpoint probe unreadable (backend=$backend)"
|
|
819
|
+
;;
|
|
820
|
+
unverified-harness)
|
|
821
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: recorded harness '$harness' is unverified for recovery (backend=$backend)"
|
|
822
|
+
;;
|
|
823
|
+
*)
|
|
824
|
+
echo "SECONDMATE_LIVENESS: secondmate $id: skipped: agent recovery classifier unverified (backend=$backend)"
|
|
825
|
+
;;
|
|
826
|
+
esac
|
|
827
|
+
return 0
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
secondmate_handoff_resume() {
|
|
831
|
+
[ -d "$DATA/handoff" ] || return 0
|
|
832
|
+
"$SCRIPT_DIR/fm-backlog-handoff.sh" --resume-pending >/dev/null 2>&1 || true
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
secondmate_handoff_detect() {
|
|
836
|
+
local outbox id count
|
|
837
|
+
[ -d "$DATA/handoff" ] || return 0
|
|
838
|
+
for outbox in "$DATA/handoff"/*.outbox.md; do
|
|
839
|
+
[ -e "$outbox" ] || continue
|
|
840
|
+
id=$(basename "$outbox" .outbox.md)
|
|
841
|
+
case "$id" in ''|*[!A-Za-z0-9._-]*) id=unknown ;; esac
|
|
842
|
+
if [ ! -f "$outbox" ] || [ -L "$outbox" ]; then
|
|
843
|
+
echo "SECONDMATE_HANDOFF: secondmate $id: pending delivery: unsafe outbox"
|
|
844
|
+
continue
|
|
845
|
+
fi
|
|
846
|
+
count=$(awk '/^- \[[ x]\] / { count++ } END { print count + 0 }' "$outbox" 2>/dev/null || printf unknown)
|
|
847
|
+
echo "SECONDMATE_HANDOFF: secondmate $id: pending delivery: $count item(s)"
|
|
848
|
+
done
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
install_cmd() {
|
|
852
|
+
case "$1" in
|
|
853
|
+
tmux|node|git|gh|curl|jq|orca|zellij) echo "brew install $1 # or the platform's package manager" ;;
|
|
854
|
+
cmux) echo "brew install --cask cmux # or see https://cmux.com" ;;
|
|
855
|
+
treehouse) echo "curl -fsSL https://kunchenguid.github.io/treehouse/install.sh | sh" ;;
|
|
856
|
+
no-mistakes) echo "curl -fsSL https://raw.githubusercontent.com/kunchenguid/no-mistakes/main/docs/install.sh | sh" ;;
|
|
857
|
+
gh-axi|chrome-devtools-axi|lavish-axi) echo "npm install -g $1 && $1 setup hooks" ;;
|
|
858
|
+
tasks-axi|quota-axi) echo "npm install -g $1" ;;
|
|
859
|
+
*) return 1 ;;
|
|
860
|
+
esac
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
manual_install_url() {
|
|
864
|
+
case "$1" in
|
|
865
|
+
herdr) echo "https://herdr.dev" ;;
|
|
866
|
+
cursor-agent) echo "https://cursor.com/cli" ;;
|
|
867
|
+
*) return 1 ;;
|
|
868
|
+
esac
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
missing_tool_diagnostic() {
|
|
872
|
+
local tool=$1 instructions
|
|
873
|
+
if instructions=$(manual_install_url "$tool"); then
|
|
874
|
+
echo "MISSING_MANUAL: $tool (instructions: $instructions)"
|
|
875
|
+
return 0
|
|
876
|
+
fi
|
|
877
|
+
echo "MISSING: $tool (install: $(install_cmd "$tool"))"
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
# Required-tool detection follows the RESOLVED backend, not a one-size default:
|
|
881
|
+
# a universal toolchain every home needs plus the backend-specific delta owned by
|
|
882
|
+
# fm_backend_required_tools (bin/fm-backend.sh). So a herdr/zellij/cmux home is
|
|
883
|
+
# never told tmux is missing, and only orca drops treehouse. A backend value with
|
|
884
|
+
# no verified dependency set is reported before the universal checks continue.
|
|
885
|
+
COMMON_TOOLS="node git gh no-mistakes gh-axi chrome-devtools-axi lavish-axi tasks-axi quota-axi"
|
|
886
|
+
BACKEND=$(fm_backend_name)
|
|
887
|
+
BACKEND_VALID=1
|
|
888
|
+
if ! BACKEND_TOOLS=$(fm_backend_required_tools "$BACKEND"); then
|
|
889
|
+
BACKEND_VALID=0
|
|
890
|
+
BACKEND_TOOLS=""
|
|
891
|
+
fi
|
|
892
|
+
TOOLS="$BACKEND_TOOLS $COMMON_TOOLS"
|
|
893
|
+
NO_MISTAKES_MIN=1.46.0
|
|
894
|
+
# AXI-FAMILY FLOOR POLICY. Every axi-family floor is the CURRENT LATEST published
|
|
895
|
+
# version of that tool, captain-bumped periodically to keep the whole fleet on the
|
|
896
|
+
# newest axi tools. It is NOT the minimum feature-introduced version. These floors
|
|
897
|
+
# are expected to drift upward as new versions ship. Never lower a floor to the
|
|
898
|
+
# earliest release that happens to satisfy some depended-on behavior. The
|
|
899
|
+
# tasks-axi feature probes are an independent defense-in-depth concern, not part
|
|
900
|
+
# of its floor.
|
|
901
|
+
GH_AXI_MIN=0.1.29
|
|
902
|
+
LAVISH_AXI_MIN=0.1.46
|
|
903
|
+
|
|
904
|
+
treehouse_supports_lease() {
|
|
905
|
+
treehouse get --help 2>&1 | grep -Eq '(^|[^[:alnum:]_-])--lease([^[:alnum:]_-]|$)'
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
# Shared semantic-version floor for the tool gates below. A version string that
|
|
909
|
+
# cannot be parsed into exactly one major.minor.patch triple is incompatible,
|
|
910
|
+
# never assumed current, so a development or vendored build cannot pass a floor
|
|
911
|
+
# it was never checked against.
|
|
912
|
+
tool_version_at_least() { # <tool> <min-version>
|
|
913
|
+
local tool=$1 min=$2 output parts major minor patch extra
|
|
914
|
+
local min_major min_minor min_patch min_extra
|
|
915
|
+
command -v "$tool" >/dev/null 2>&1 || return 1
|
|
916
|
+
output=$("$tool" --version 2>/dev/null) || return 1
|
|
917
|
+
parts=$(printf '%s\n' "$output" | sed -nE 's/.*[vV]?([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 \2 \3/p' | head -n 1)
|
|
918
|
+
IFS=' ' read -r major minor patch extra <<< "$parts"
|
|
919
|
+
[ -n "$major" ] && [ -n "$minor" ] && [ -n "$patch" ] && [ -z "$extra" ] || return 1
|
|
920
|
+
IFS='.' read -r min_major min_minor min_patch min_extra <<< "$min"
|
|
921
|
+
[ -n "$min_major" ] && [ -n "$min_minor" ] && [ -n "$min_patch" ] && [ -z "$min_extra" ] || return 1
|
|
922
|
+
[ "$major" -gt "$min_major" ] && return 0
|
|
923
|
+
[ "$major" -eq "$min_major" ] || return 1
|
|
924
|
+
[ "$minor" -gt "$min_minor" ] && return 0
|
|
925
|
+
[ "$minor" -eq "$min_minor" ] || return 1
|
|
926
|
+
[ "$patch" -ge "$min_patch" ]
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
x_mode_write_if_changed() {
|
|
930
|
+
local dest=$1 content=$2 mode=$3 parent tmp parent_device current_mode
|
|
931
|
+
parent=${dest%/*}
|
|
932
|
+
[ "$parent" != "$dest" ] || return 1
|
|
933
|
+
[ -d "$parent" ] && [ ! -L "$parent" ] || return 1
|
|
934
|
+
if [ "$(uname)" = Darwin ]; then
|
|
935
|
+
parent_device=$(stat -f %d "$parent" 2>/dev/null) || return 1
|
|
936
|
+
else
|
|
937
|
+
parent_device=$(stat -c %d "$parent" 2>/dev/null) || return 1
|
|
938
|
+
fi
|
|
939
|
+
if [ -e "$dest" ] || [ -L "$dest" ]; then
|
|
940
|
+
fmx_single_link_file_valid "$dest" "$parent_device" || return 1
|
|
941
|
+
if [ "$(uname)" = Darwin ]; then
|
|
942
|
+
current_mode=$(stat -f %Lp "$dest" 2>/dev/null) || return 1
|
|
943
|
+
else
|
|
944
|
+
current_mode=$(stat -c %a "$dest" 2>/dev/null) || return 1
|
|
945
|
+
fi
|
|
946
|
+
if [ "$current_mode" = "$mode" ] && cmp -s "$dest" <(printf '%s\n' "$content"); then
|
|
947
|
+
return 0
|
|
948
|
+
fi
|
|
949
|
+
fi
|
|
950
|
+
tmp=$(umask 077; mktemp "$parent/.fm-x-mode.XXXXXX" 2>/dev/null) || return 1
|
|
951
|
+
if ! printf '%s\n' "$content" > "$tmp" \
|
|
952
|
+
|| ! chmod "$mode" "$tmp" \
|
|
953
|
+
|| ! fmx_single_link_file_mode_valid "$tmp" "$mode" "$parent_device"; then
|
|
954
|
+
rm -f -- "$tmp"
|
|
955
|
+
return 1
|
|
956
|
+
fi
|
|
957
|
+
if { [ -e "$dest" ] || [ -L "$dest" ]; } \
|
|
958
|
+
&& ! fmx_single_link_file_valid "$dest" "$parent_device"; then
|
|
959
|
+
rm -f -- "$tmp"
|
|
960
|
+
return 1
|
|
961
|
+
fi
|
|
962
|
+
if ! mv -f -- "$tmp" "$dest"; then
|
|
963
|
+
rm -f -- "$tmp"
|
|
964
|
+
return 1
|
|
965
|
+
fi
|
|
966
|
+
if ! fmx_single_link_file_mode_valid "$dest" "$mode" "$parent_device" \
|
|
967
|
+
|| ! cmp -s "$dest" <(printf '%s\n' "$content"); then
|
|
968
|
+
rm -f -- "$dest"
|
|
969
|
+
return 1
|
|
970
|
+
fi
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
x_mode_artifact_present() {
|
|
974
|
+
[ -e "$1" ] || [ -L "$1" ]
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
x_mode_remove_artifact() {
|
|
978
|
+
local artifact=$1 parent=${1%/*}
|
|
979
|
+
x_mode_artifact_present "$artifact" || return 0
|
|
980
|
+
[ -d "$parent" ] && [ ! -L "$parent" ] || return 1
|
|
981
|
+
rm -f -- "$artifact" 2>/dev/null || return 1
|
|
982
|
+
! x_mode_artifact_present "$artifact"
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
# X mode (opt-in): when this home's .env carries a non-empty FMX_PAIRING_TOKEN,
|
|
986
|
+
# wire the relay poll into the existing authenticated watcher dispatch.
|
|
987
|
+
# Drops two idempotent, gitignored artifacts:
|
|
988
|
+
# state/x-watch.check.sh - byte-static identity shim; the watcher validates
|
|
989
|
+
# its bytes and invokes bin/fm-x-poll.sh directly
|
|
990
|
+
# config/x-mode.env - exports FM_CHECK_INTERVAL=30, sourced by the watcher
|
|
991
|
+
# arm so only an X instance polls at the 30s cadence
|
|
992
|
+
# On opt-out (no token, or empty) it removes any such artifacts so the instance
|
|
993
|
+
# reverts to the default 300s no-poll behavior. Absent a token AND with no leftover
|
|
994
|
+
# artifacts it is a complete no-op (nothing written, nothing printed), so a non-X
|
|
995
|
+
# user sees zero change. Prints one confirmation line on opt-in, and one on opt-out
|
|
996
|
+
# only when it actually removed artifacts. It never touches the watcher itself;
|
|
997
|
+
# applying a cadence transition to a running watcher is the caller's job via
|
|
998
|
+
# the emitted harness-aware supervision repair instruction.
|
|
999
|
+
x_mode_setup() {
|
|
1000
|
+
local env_file token shim cadence shim_body cadence_body tool missing shim_home
|
|
1001
|
+
env_file="$FM_HOME/.env"
|
|
1002
|
+
shim="$STATE/x-watch.check.sh"
|
|
1003
|
+
cadence="$CONFIG/x-mode.env"
|
|
1004
|
+
|
|
1005
|
+
token=
|
|
1006
|
+
[ -f "$env_file" ] && token=$(fmx_env_get FMX_PAIRING_TOKEN "$env_file")
|
|
1007
|
+
|
|
1008
|
+
x_mode_remove_artifacts() {
|
|
1009
|
+
local failed=0
|
|
1010
|
+
x_mode_remove_artifact "$shim" || failed=1
|
|
1011
|
+
x_mode_remove_artifact "$cadence" || failed=1
|
|
1012
|
+
[ "$failed" -eq 0 ]
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
x_mode_supervision_repair() {
|
|
1016
|
+
local out
|
|
1017
|
+
out=$("$SCRIPT_DIR/fm-supervision-instructions.sh" --repair-line 2>/dev/null) \
|
|
1018
|
+
|| out='repair missing watcher supervision according to the session-start operating block.'
|
|
1019
|
+
printf '%s\n' "$out"
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if [ -z "$token" ]; then
|
|
1023
|
+
# Opt-out (or never opted in): drop any X artifacts; stay silent unless we
|
|
1024
|
+
# actually removed something.
|
|
1025
|
+
if x_mode_artifact_present "$shim" || x_mode_artifact_present "$cadence"; then
|
|
1026
|
+
if x_mode_remove_artifacts; then
|
|
1027
|
+
echo "FMX: X mode off - removed relay poll shim and 30s cadence; default cadence applies on the next supervision cycle; $(x_mode_supervision_repair)"
|
|
1028
|
+
else
|
|
1029
|
+
echo "FMX: X mode off - failed to remove relay poll shim or 30s cadence"
|
|
1030
|
+
fi
|
|
1031
|
+
fi
|
|
1032
|
+
return 0
|
|
1033
|
+
fi
|
|
1034
|
+
|
|
1035
|
+
missing=0
|
|
1036
|
+
for tool in curl jq; do
|
|
1037
|
+
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
1038
|
+
echo "MISSING: $tool (install: $(install_cmd "$tool"))"
|
|
1039
|
+
missing=1
|
|
1040
|
+
fi
|
|
1041
|
+
done
|
|
1042
|
+
if [ "$missing" -ne 0 ]; then
|
|
1043
|
+
if x_mode_artifact_present "$shim" || x_mode_artifact_present "$cadence"; then
|
|
1044
|
+
if x_mode_remove_artifacts; then
|
|
1045
|
+
echo "FMX: X mode off - missing relay poll dependencies; install them and rerun bootstrap"
|
|
1046
|
+
else
|
|
1047
|
+
echo "FMX: X mode off - failed to remove relay poll shim or 30s cadence after missing relay poll dependencies"
|
|
1048
|
+
fi
|
|
1049
|
+
fi
|
|
1050
|
+
return 0
|
|
1051
|
+
fi
|
|
1052
|
+
|
|
1053
|
+
fmx_arm_failed() {
|
|
1054
|
+
if x_mode_remove_artifacts; then
|
|
1055
|
+
echo "FMX: X mode off - failed to arm relay poll shim or 30s cadence"
|
|
1056
|
+
else
|
|
1057
|
+
echo "FMX: X mode off - failed to arm relay poll shim or 30s cadence; stale artifacts remain"
|
|
1058
|
+
fi
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
mkdir -p "$STATE" "$CONFIG" 2>/dev/null || { fmx_arm_failed; return 0; }
|
|
1062
|
+
|
|
1063
|
+
case "$FM_HOME" in
|
|
1064
|
+
/*) shim_home=$FM_HOME ;;
|
|
1065
|
+
*)
|
|
1066
|
+
shim_home=$(CDPATH='' cd -- "$FM_HOME" 2>/dev/null && pwd -P) \
|
|
1067
|
+
|| { fmx_arm_failed; return 0; }
|
|
1068
|
+
;;
|
|
1069
|
+
esac
|
|
1070
|
+
shim_body=$(fmx_poll_shim_content "$shim_home" "$FM_ROOT")
|
|
1071
|
+
x_mode_write_if_changed "$shim" "$shim_body" 700 || { fmx_arm_failed; return 0; }
|
|
1072
|
+
fmx_poll_shim_valid "$shim" "$shim_home" "$FM_ROOT" \
|
|
1073
|
+
|| { fmx_arm_failed; return 0; }
|
|
1074
|
+
|
|
1075
|
+
cadence_body=$(cat <<'EOF'
|
|
1076
|
+
# Auto-generated by fm-bootstrap.sh - X mode watcher cadence.
|
|
1077
|
+
# Source this before the active harness protocol starts a watcher process so
|
|
1078
|
+
# fm-watch.sh polls the X check every 30s. Non-X instances have no such file and
|
|
1079
|
+
# keep the default 300s cadence.
|
|
1080
|
+
export FM_CHECK_INTERVAL=30
|
|
1081
|
+
EOF
|
|
1082
|
+
)
|
|
1083
|
+
x_mode_write_if_changed "$cadence" "$cadence_body" 600 || { fmx_arm_failed; return 0; }
|
|
1084
|
+
|
|
1085
|
+
echo "FMX: X mode on - relay poll armed via state/x-watch.check.sh; 30s watcher cadence in config/x-mode.env"
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
crew_dispatch_validate() {
|
|
1089
|
+
local file err
|
|
1090
|
+
file="$CONFIG/crew-dispatch.json"
|
|
1091
|
+
[ -f "$file" ] || return 0
|
|
1092
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
1093
|
+
echo "MISSING: jq (install: $(install_cmd jq))"
|
|
1094
|
+
return 0
|
|
1095
|
+
fi
|
|
1096
|
+
if ! jq -e . "$file" >/dev/null 2>&1; then
|
|
1097
|
+
echo "CREW_DISPATCH: invalid config/crew-dispatch.json - malformed JSON"
|
|
1098
|
+
return 0
|
|
1099
|
+
fi
|
|
1100
|
+
err=$(jq -r '
|
|
1101
|
+
def verified($h): ["claude","codex","opencode","pi","pi-signed","grok","kimi","cursor","muse"] | index($h);
|
|
1102
|
+
def effort_ok($h; $e):
|
|
1103
|
+
if $e == null then true
|
|
1104
|
+
elif ($e | type) != "string" then false
|
|
1105
|
+
elif $h == "claude" then (["low","medium","high","xhigh","max"] | index($e))
|
|
1106
|
+
elif $h == "codex" then (["low","medium","high","xhigh"] | index($e))
|
|
1107
|
+
elif $h == "grok" then (["low","medium","high"] | index($e))
|
|
1108
|
+
elif $h == "pi" or $h == "pi-signed" then (["low","medium","high","xhigh","max"] | index($e))
|
|
1109
|
+
elif $h == "muse" then (["low","medium","high","xhigh","max"] | index($e))
|
|
1110
|
+
elif $h == "opencode" or $h == "kimi" or $h == "cursor" then false
|
|
1111
|
+
else true
|
|
1112
|
+
end;
|
|
1113
|
+
def profiles($value):
|
|
1114
|
+
if ($value | type) == "array" then $value
|
|
1115
|
+
elif ($value | type) == "object" then [$value]
|
|
1116
|
+
else []
|
|
1117
|
+
end;
|
|
1118
|
+
def configured_profiles:
|
|
1119
|
+
([(.rules // [])[]? | profiles(.use?)[]?]
|
|
1120
|
+
+ (if has("default") then [profiles(.default)[]?] else [] end));
|
|
1121
|
+
def malformed_optional_fields($items):
|
|
1122
|
+
($items | any(has("model") and (((.model | type) != "string") or (.model | length) == 0)))
|
|
1123
|
+
or ($items | any(has("effort") and (((.effort | type) != "string") or (.effort | length) == 0)));
|
|
1124
|
+
def bad_efforts:
|
|
1125
|
+
configured_profiles
|
|
1126
|
+
| map({h: .harness, e: .effort})
|
|
1127
|
+
| map(select(.e != null))
|
|
1128
|
+
| map(select((.h | type) == "string" and verified(.h)))
|
|
1129
|
+
| map(select(. as $p | effort_ok($p.h; $p.e) | not))
|
|
1130
|
+
| map("\(.h):\(.e)")
|
|
1131
|
+
| unique;
|
|
1132
|
+
if type != "object" then "top-level value must be an object"
|
|
1133
|
+
elif has("rules") and (.rules | type) != "array" then "rules must be an array"
|
|
1134
|
+
elif [(.rules // [])[]? | select(type != "object")] | length > 0 then "each rule must be an object"
|
|
1135
|
+
elif [(.rules // [])[]? | select((.when? | type) != "string" or (.when | length) == 0)] | length > 0 then "each rule needs non-empty when"
|
|
1136
|
+
elif [(.rules // [])[]? | select((.use? | type) != "object" and (.use? | type) != "array")] | length > 0 then "each rule needs use"
|
|
1137
|
+
elif [(.rules // [])[]? | select((.use? | type) == "array" and (.use | length) == 0)] | length > 0 then "each rule needs at least one use profile"
|
|
1138
|
+
elif [(.rules // [])[]? | profiles(.use?)[]? | select(type != "object")] | length > 0 then "each use profile must be an object"
|
|
1139
|
+
elif [(.rules // [])[]? | profiles(.use?)[]? | select((.harness? | type) != "string" or (.harness | length) == 0)] | length > 0 then "each use profile needs harness"
|
|
1140
|
+
elif malformed_optional_fields([(.rules // [])[]? | profiles(.use?)[]?]) then "use profile model and effort must be non-empty strings when present"
|
|
1141
|
+
elif [(.rules // [])[]? | select(has("select") and ((.select? | type) != "string" or (.select | length) == 0))] | length > 0 then "select must be a non-empty string"
|
|
1142
|
+
elif [(.rules // [])[]? | .select? // empty | select(. != "quota-balanced")] | length > 0 then
|
|
1143
|
+
"unknown select: " + ([ (.rules // [])[]? | .select? // empty | select(. != "quota-balanced") ] | unique | join(", "))
|
|
1144
|
+
elif has("default") and ((.default | type) != "object" and (.default | type) != "array") then "default must be a profile object or non-empty profile array"
|
|
1145
|
+
elif has("default") and ((.default | type) == "array" and (.default | length) == 0) then "default needs at least one profile"
|
|
1146
|
+
elif has("default") and ([profiles(.default)[]? | select(type != "object")] | length) > 0 then "each default profile must be an object"
|
|
1147
|
+
elif has("default") and ([profiles(.default)[]? | select((.harness? | type) != "string" or (.harness | length) == 0)] | length) > 0 then "each default profile needs harness"
|
|
1148
|
+
elif has("default") and malformed_optional_fields([profiles(.default)[]?]) then "default profile model and effort must be non-empty strings when present"
|
|
1149
|
+
else
|
|
1150
|
+
(configured_profiles
|
|
1151
|
+
| map(.harness)
|
|
1152
|
+
| map(select(. != null))
|
|
1153
|
+
| map(select(. as $h | verified($h) | not))
|
|
1154
|
+
| unique) as $bad_harnesses
|
|
1155
|
+
| if ($bad_harnesses | length) > 0 then "unverified harness: " + ($bad_harnesses | join(", "))
|
|
1156
|
+
elif (bad_efforts | length) > 0 then "invalid effort: " + (bad_efforts | join(", "))
|
|
1157
|
+
else empty
|
|
1158
|
+
end
|
|
1159
|
+
end
|
|
1160
|
+
' "$file" 2>/dev/null || true)
|
|
1161
|
+
if [ -n "$err" ]; then
|
|
1162
|
+
echo "CREW_DISPATCH: invalid config/crew-dispatch.json - $err"
|
|
1163
|
+
return 0
|
|
1164
|
+
fi
|
|
1165
|
+
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ]; then
|
|
1166
|
+
jq -r '
|
|
1167
|
+
def profile($p):
|
|
1168
|
+
($p.harness | tostring)
|
|
1169
|
+
+ (if ($p.model? != null) then "/" + ($p.model | tostring)
|
|
1170
|
+
elif ($p.effort? != null) then "/default"
|
|
1171
|
+
else "" end)
|
|
1172
|
+
+ (if ($p.effort? != null) then "/" + ($p.effort | tostring) else "" end);
|
|
1173
|
+
def profile_set($value; $selector):
|
|
1174
|
+
if ($value | type) == "array" then
|
|
1175
|
+
(($selector // "quota-balanced") + "[" + ([$value[] | profile(.)] | join(", ")) + "]")
|
|
1176
|
+
else profile($value)
|
|
1177
|
+
end;
|
|
1178
|
+
(["BOOTSTRAP_INFO: crew dispatch active config/crew-dispatch.json"]
|
|
1179
|
+
+ [(.rules // [])[]? | "BOOTSTRAP_INFO: crew dispatch rule: " + (.when | tostring) + " -> " + profile_set(.use; .select?)]
|
|
1180
|
+
+ (if has("default") then ["BOOTSTRAP_INFO: crew dispatch default: " + profile_set(.default; null)] else [] end))
|
|
1181
|
+
| .[]
|
|
1182
|
+
' "$file"
|
|
1183
|
+
fi
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
# Same-home record reconciliation. Every ordinary dispatch and completion now
|
|
1187
|
+
# moves the backlog row inside the script that moves the task's record
|
|
1188
|
+
# (bin/fm-backlog-transition-lib.sh), so remaining recovery cases include a
|
|
1189
|
+
# process killed mid-transition and drift this home was already carrying. Heal
|
|
1190
|
+
# this home's OWN books on its own
|
|
1191
|
+
# restart rather than waiting for a parent's cross-home nudge; the fleet
|
|
1192
|
+
# snapshot's classifier and bin/fm-secondmate-reconcile.sh's nudge stay as
|
|
1193
|
+
# backstops for what this cannot see. Never reads or writes another home.
|
|
1194
|
+
backlog_record_reconcile() {
|
|
1195
|
+
local marker meta meta_lock id row label has_record=0 gate_status
|
|
1196
|
+
# A fresh home with no state directory has no physical task records to pair.
|
|
1197
|
+
# Keep bootstrap diagnostics working without creating state just for a no-op.
|
|
1198
|
+
[ -e "$STATE" ] || [ -L "$STATE" ] || return 0
|
|
1199
|
+
if ! fm_backlog_directory_present "$STATE" "state directory"; then
|
|
1200
|
+
echo "error: backlog reconciliation refused: $FM_BACKLOG_TRANSITION_ERROR" >&2
|
|
1201
|
+
return 2
|
|
1202
|
+
fi
|
|
1203
|
+
if fm_backlog_transition_applies "$CONFIG" "$DATA" "$BOOTSTRAP_BACKLOG_GATE_KIND"; then
|
|
1204
|
+
:
|
|
1205
|
+
else
|
|
1206
|
+
gate_status=$?
|
|
1207
|
+
if [ "$gate_status" -eq 2 ]; then
|
|
1208
|
+
echo "error: backlog reconciliation cannot access configured data directory $DATA ($FM_BACKLOG_TRANSITION_ERROR)" >&2
|
|
1209
|
+
return 2
|
|
1210
|
+
fi
|
|
1211
|
+
return 0
|
|
1212
|
+
fi
|
|
1213
|
+
# Keep the wake/lock library's source-time state-directory creation inside
|
|
1214
|
+
# this mutating sweep, so FM_BOOTSTRAP_DETECT_ONLY remains read-only.
|
|
1215
|
+
# shellcheck source=bin/fm-wake-lib.sh disable=SC1091
|
|
1216
|
+
. "$SCRIPT_DIR/fm-wake-lib.sh"
|
|
1217
|
+
|
|
1218
|
+
# Finish any close an interrupted cleanup recorded but never landed.
|
|
1219
|
+
for marker in "$STATE"/*.backlog-close; do
|
|
1220
|
+
[ -e "$marker" ] || [ -L "$marker" ] || continue
|
|
1221
|
+
if ! fm_backlog_record_present "$marker" "pending-close record" "$STATE"; then
|
|
1222
|
+
echo "BACKLOG_RECONCILE: unsafe pending close refused: $FM_BACKLOG_TRANSITION_ERROR"
|
|
1223
|
+
return 2
|
|
1224
|
+
fi
|
|
1225
|
+
label=$(basename "$marker" .backlog-close)
|
|
1226
|
+
meta_lock=$(fm_meta_lock_path "$STATE/$label.meta") || continue
|
|
1227
|
+
fm_lock_try_acquire "$meta_lock" || continue
|
|
1228
|
+
if fm_backlog_close_marker_replay "$STATE" "$marker" "$DATA"; then
|
|
1229
|
+
case "$FM_BACKLOG_CLOSE_REPLAY_RESULT" in
|
|
1230
|
+
closed)
|
|
1231
|
+
echo "BOOTSTRAP_INFO: closed the backlog item for $label that an interrupted cleanup left open"
|
|
1232
|
+
;;
|
|
1233
|
+
closed_incomplete)
|
|
1234
|
+
echo "BOOTSTRAP_INFO: closed the backlog item for $label after interrupted cleanup; its endpoint or local copy may remain and should be reconciled"
|
|
1235
|
+
;;
|
|
1236
|
+
esac
|
|
1237
|
+
else
|
|
1238
|
+
echo "BACKLOG_RECONCILE: $label: recorded backlog close could not be replayed: $FM_BACKLOG_TRANSITION_ERROR"
|
|
1239
|
+
fi
|
|
1240
|
+
fm_lock_release "$meta_lock"
|
|
1241
|
+
done
|
|
1242
|
+
|
|
1243
|
+
# A home that owns no records has nothing to pair, so it never pays for a
|
|
1244
|
+
# backlog read. A pending close remains authoritative even when replay failed:
|
|
1245
|
+
# the record sweep below must not start that item while its marker survives.
|
|
1246
|
+
for meta in "$STATE"/*.meta; do
|
|
1247
|
+
[ -e "$meta" ] || [ -L "$meta" ] || continue
|
|
1248
|
+
if ! fm_backlog_record_present "$meta" "task record" "$STATE"; then
|
|
1249
|
+
echo "BACKLOG_RECONCILE: unsafe worker record refused: $FM_BACKLOG_TRANSITION_ERROR"
|
|
1250
|
+
return 2
|
|
1251
|
+
fi
|
|
1252
|
+
has_record=1
|
|
1253
|
+
break
|
|
1254
|
+
done
|
|
1255
|
+
[ "$has_record" = 1 ] || return 0
|
|
1256
|
+
for meta in "$STATE"/*.meta; do
|
|
1257
|
+
[ -e "$meta" ] || [ -L "$meta" ] || continue
|
|
1258
|
+
if ! fm_backlog_record_present "$meta" "task record" "$STATE"; then
|
|
1259
|
+
echo "BACKLOG_RECONCILE: unsafe worker record refused: $FM_BACKLOG_TRANSITION_ERROR"
|
|
1260
|
+
return 2
|
|
1261
|
+
fi
|
|
1262
|
+
id=$(basename "$meta" .meta)
|
|
1263
|
+
meta_lock=$(fm_meta_lock_path "$meta") || continue
|
|
1264
|
+
fm_lock_try_acquire "$meta_lock" || continue
|
|
1265
|
+
if [ -e "$STATE/$id.backlog-close" ] || [ -L "$STATE/$id.backlog-close" ]; then
|
|
1266
|
+
fm_lock_release "$meta_lock"
|
|
1267
|
+
continue
|
|
1268
|
+
fi
|
|
1269
|
+
if ! fm_backlog_record_present "$meta" "task record" "$STATE"; then
|
|
1270
|
+
echo "BACKLOG_RECONCILE: $id: post-lock worker record check refused: $FM_BACKLOG_TRANSITION_ERROR"
|
|
1271
|
+
fm_lock_release "$meta_lock"
|
|
1272
|
+
return 2
|
|
1273
|
+
fi
|
|
1274
|
+
if [ "$(fm_meta_get "$meta" kind)" != secondmate ] \
|
|
1275
|
+
&& [ "$(fm_meta_get "$meta" cleanup_recovery)" != orca ]; then
|
|
1276
|
+
row=
|
|
1277
|
+
if fm_backlog_row_probe "$DATA" "$id"; then
|
|
1278
|
+
row=$FM_BACKLOG_ROW_STATE
|
|
1279
|
+
elif [ "$FM_BACKLOG_ROW_RESULT" != not_found ]; then
|
|
1280
|
+
echo "BACKLOG_RECONCILE: $id: worker record exists but its backlog item could not be read: $FM_BACKLOG_ROW_ERROR"
|
|
1281
|
+
fi
|
|
1282
|
+
# Heal only the unambiguous case: a queued row for a record this home
|
|
1283
|
+
# already owns. A held row is the captain's to move, and a closed row is a
|
|
1284
|
+
# contradiction this sweep must not resolve by resurrecting the item.
|
|
1285
|
+
if [ "$row" = "queued no no" ]; then
|
|
1286
|
+
if fm_backlog_start "$DATA" "$id"; then
|
|
1287
|
+
echo "BOOTSTRAP_INFO: marked $id in flight to match the worker this home already owns"
|
|
1288
|
+
else
|
|
1289
|
+
echo "BACKLOG_RECONCILE: $id: worker record exists but its backlog item could not be moved to In flight: $FM_BACKLOG_TRANSITION_ERROR"
|
|
1290
|
+
fi
|
|
1291
|
+
fi
|
|
1292
|
+
fi
|
|
1293
|
+
fm_lock_release "$meta_lock"
|
|
1294
|
+
done
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
startup_memory_budget_setup() {
|
|
1298
|
+
# Primary bootstrap owns default publication. A secondmate is deliberately
|
|
1299
|
+
# passive here because its setting must converge from the primary through the
|
|
1300
|
+
# inherited-local-material contract rather than becoming a local authority.
|
|
1301
|
+
if [ -e "$FM_HOME/.fm-secondmate-home" ] || [ -L "$FM_HOME/.fm-secondmate-home" ]; then
|
|
1302
|
+
return 0
|
|
1303
|
+
fi
|
|
1304
|
+
if ! fm_startup_memory_budget_materialize "$CONFIG"; then
|
|
1305
|
+
echo "STARTUP_MEMORY_BUDGET: invalid config/$FM_STARTUP_MEMORY_BUDGET_FILE - $FM_STARTUP_MEMORY_BUDGET_ERROR"
|
|
1306
|
+
fi
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
if [ "${1:-}" = "install" ]; then
|
|
1310
|
+
shift
|
|
1311
|
+
[ $# -gt 0 ] || { echo "usage: fm-bootstrap.sh install <tool>..." >&2; exit 1; }
|
|
1312
|
+
for t in "$@"; do
|
|
1313
|
+
if ! cmd=$(install_cmd "$t"); then
|
|
1314
|
+
instructions=$(manual_install_url "$t") || { echo "error: unknown tool $t" >&2; exit 1; }
|
|
1315
|
+
echo "error: $t requires manual installation (instructions: $instructions)" >&2
|
|
1316
|
+
exit 1
|
|
1317
|
+
fi
|
|
1318
|
+
cmd=${cmd%% #*}
|
|
1319
|
+
echo "installing $t: $cmd"
|
|
1320
|
+
eval "$cmd"
|
|
1321
|
+
done
|
|
1322
|
+
exit 0
|
|
1323
|
+
fi
|
|
1324
|
+
|
|
1325
|
+
# This is the first mutating sweep at a locked session boundary. Detect-only
|
|
1326
|
+
# sessions never touch state, and the deferred network pass never repeats it:
|
|
1327
|
+
# the local pass that ran first already closed that window.
|
|
1328
|
+
if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ] && local_phase; then
|
|
1329
|
+
BOOTSTRAP_BACKLOG_GATE_KIND=secondmate
|
|
1330
|
+
if [ -e "$STATE" ] || [ -L "$STATE" ]; then
|
|
1331
|
+
if ! fm_backlog_directory_present "$STATE" "state directory"; then
|
|
1332
|
+
echo "error: bootstrap cannot reconcile task state ($FM_BACKLOG_TRANSITION_ERROR)" >&2
|
|
1333
|
+
exit 1
|
|
1334
|
+
fi
|
|
1335
|
+
for BOOTSTRAP_BACKLOG_MARKER in "$STATE"/*.backlog-close; do
|
|
1336
|
+
[ -e "$BOOTSTRAP_BACKLOG_MARKER" ] || [ -L "$BOOTSTRAP_BACKLOG_MARKER" ] || continue
|
|
1337
|
+
if ! fm_backlog_record_present "$BOOTSTRAP_BACKLOG_MARKER" "pending-close record" "$STATE"; then
|
|
1338
|
+
echo "error: bootstrap refused unsafe pending close ($FM_BACKLOG_TRANSITION_ERROR)" >&2
|
|
1339
|
+
exit 1
|
|
1340
|
+
fi
|
|
1341
|
+
BOOTSTRAP_BACKLOG_GATE_KIND=ship
|
|
1342
|
+
break
|
|
1343
|
+
done
|
|
1344
|
+
if [ "$BOOTSTRAP_BACKLOG_GATE_KIND" = secondmate ]; then
|
|
1345
|
+
for BOOTSTRAP_BACKLOG_META in "$STATE"/*.meta; do
|
|
1346
|
+
[ -e "$BOOTSTRAP_BACKLOG_META" ] || [ -L "$BOOTSTRAP_BACKLOG_META" ] || continue
|
|
1347
|
+
if ! fm_backlog_record_present "$BOOTSTRAP_BACKLOG_META" "task record" "$STATE"; then
|
|
1348
|
+
echo "error: bootstrap refused unsafe worker record ($FM_BACKLOG_TRANSITION_ERROR)" >&2
|
|
1349
|
+
exit 1
|
|
1350
|
+
fi
|
|
1351
|
+
if [ "$(fm_meta_get "$BOOTSTRAP_BACKLOG_META" kind)" != secondmate ] \
|
|
1352
|
+
&& [ "$(fm_meta_get "$BOOTSTRAP_BACKLOG_META" cleanup_recovery)" != orca ]; then
|
|
1353
|
+
BOOTSTRAP_BACKLOG_GATE_KIND=ship
|
|
1354
|
+
break
|
|
1355
|
+
fi
|
|
1356
|
+
done
|
|
1357
|
+
fi
|
|
1358
|
+
fi
|
|
1359
|
+
if fm_backlog_transition_applies "$CONFIG" "$DATA" "$BOOTSTRAP_BACKLOG_GATE_KIND"; then
|
|
1360
|
+
:
|
|
1361
|
+
else
|
|
1362
|
+
BOOTSTRAP_BACKLOG_GATE_STATUS=$?
|
|
1363
|
+
if [ "$BOOTSTRAP_BACKLOG_GATE_STATUS" -eq 2 ]; then
|
|
1364
|
+
echo "error: bootstrap cannot access configured backlog data directory $DATA ($FM_BACKLOG_TRANSITION_ERROR)" >&2
|
|
1365
|
+
exit 1
|
|
1366
|
+
fi
|
|
1367
|
+
fi
|
|
1368
|
+
startup_memory_budget_setup
|
|
1369
|
+
if backlog_record_reconcile; then
|
|
1370
|
+
:
|
|
1371
|
+
else
|
|
1372
|
+
BOOTSTRAP_BACKLOG_RECONCILE_STATUS=$?
|
|
1373
|
+
if [ "$BOOTSTRAP_BACKLOG_RECONCILE_STATUS" -eq 2 ]; then
|
|
1374
|
+
exit 1
|
|
1375
|
+
fi
|
|
1376
|
+
fi
|
|
1377
|
+
fi
|
|
1378
|
+
|
|
1379
|
+
# Local detection: presence, version floors, and configuration. Nothing here
|
|
1380
|
+
# leaves this machine, so it stays on the session-start critical path.
|
|
1381
|
+
detect_local_tools() {
|
|
1382
|
+
if [ "$BACKEND_VALID" -eq 0 ]; then
|
|
1383
|
+
echo "BACKEND_INVALID: $BACKEND (known: $FM_BACKEND_KNOWN)"
|
|
1384
|
+
fi
|
|
1385
|
+
for t in $BACKEND_TOOLS; do
|
|
1386
|
+
fm_backend_required_tool_available "$BACKEND" "$t" \
|
|
1387
|
+
|| missing_tool_diagnostic "$t"
|
|
1388
|
+
done
|
|
1389
|
+
for t in $COMMON_TOOLS; do
|
|
1390
|
+
command -v "$t" >/dev/null || missing_tool_diagnostic "$t"
|
|
1391
|
+
done
|
|
1392
|
+
# The treehouse lease-support upgrade check is only relevant when the resolved
|
|
1393
|
+
# backend actually requires treehouse (every backend except orca, which owns its
|
|
1394
|
+
# own worktrees); an orca home must not be told to upgrade a provider it never uses.
|
|
1395
|
+
if fm_backend_list_contains "$TOOLS" treehouse \
|
|
1396
|
+
&& command -v treehouse >/dev/null 2>&1 && ! treehouse_supports_lease; then
|
|
1397
|
+
echo "MISSING: treehouse (install: $(install_cmd treehouse))"
|
|
1398
|
+
fi
|
|
1399
|
+
if command -v no-mistakes >/dev/null 2>&1 && ! tool_version_at_least no-mistakes "$NO_MISTAKES_MIN"; then
|
|
1400
|
+
echo "MISSING: no-mistakes (install: $(install_cmd no-mistakes))"
|
|
1401
|
+
fi
|
|
1402
|
+
if command -v gh-axi >/dev/null 2>&1 && ! tool_version_at_least gh-axi "$GH_AXI_MIN"; then
|
|
1403
|
+
echo "MISSING: gh-axi (install: $(install_cmd gh-axi))"
|
|
1404
|
+
fi
|
|
1405
|
+
if command -v lavish-axi >/dev/null 2>&1 && ! tool_version_at_least lavish-axi "$LAVISH_AXI_MIN"; then
|
|
1406
|
+
echo "MISSING: lavish-axi (install: $(install_cmd lavish-axi))"
|
|
1407
|
+
fi
|
|
1408
|
+
if command -v quota-axi >/dev/null 2>&1 && ! fm_quota_axi_compatible; then
|
|
1409
|
+
echo "MISSING: quota-axi (install: $(install_cmd quota-axi))"
|
|
1410
|
+
fi
|
|
1411
|
+
if command -v tasks-axi >/dev/null 2>&1 && ! fm_tasks_axi_compatible; then
|
|
1412
|
+
echo "MISSING: tasks-axi (install: $(install_cmd tasks-axi))"
|
|
1413
|
+
fi
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
detect_local_config() {
|
|
1417
|
+
# Worktree-tangle check: the firstmate primary checkout (FM_ROOT) must sit on its
|
|
1418
|
+
# default branch, not a feature branch (see fm-tangle-lib.sh). Scoped to the
|
|
1419
|
+
# primary only; detached-HEAD worktrees and secondmate homes never trip it.
|
|
1420
|
+
tangle_branch=$(fm_primary_tangle_branch "$FM_ROOT" 2>/dev/null || true)
|
|
1421
|
+
if [ -n "$tangle_branch" ]; then
|
|
1422
|
+
tangle_default=$(fm_default_branch "$FM_ROOT" 2>/dev/null || echo main)
|
|
1423
|
+
if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" = 1 ] && [ "${FM_BOOTSTRAP_LOCKED:-0}" != 1 ]; then
|
|
1424
|
+
echo "TANGLE: primary checkout on feature branch '$tangle_branch' (expected '$tangle_default'); the work is safe on that ref - read-only session must leave restore work to the session holding the fleet lock"
|
|
1425
|
+
else
|
|
1426
|
+
echo "TANGLE: primary checkout on feature branch '$tangle_branch' (expected '$tangle_default'); the work is safe on that ref - restore the primary with: git -C $FM_ROOT checkout $tangle_default, then re-validate the branch in a proper worktree"
|
|
1427
|
+
fi
|
|
1428
|
+
fi
|
|
1429
|
+
crew=
|
|
1430
|
+
[ -f "$CONFIG/crew-harness" ] && crew=$(tr -d '[:space:]' < "$CONFIG/crew-harness" || true)
|
|
1431
|
+
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] && [ -n "$crew" ] && [ "$crew" != "default" ]; then
|
|
1432
|
+
echo "BOOTSTRAP_INFO: crew harness override active: $crew"
|
|
1433
|
+
fi
|
|
1434
|
+
# A configured cursor crew harness needs a cursor executable present, and
|
|
1435
|
+
# cursor ships under EITHER installed name. Resolution runs through the
|
|
1436
|
+
# verified owner rather than a bare `command -v`, so a home that merely has
|
|
1437
|
+
# some unrelated executable named `agent` on PATH is still reported missing
|
|
1438
|
+
# instead of failing at the first spawn.
|
|
1439
|
+
if [ "$crew" = cursor ] && ! fm_cursor_resolve_binary >/dev/null 2>&1; then
|
|
1440
|
+
echo "MISSING_MANUAL: cursor-agent (instructions: $(manual_install_url cursor-agent))"
|
|
1441
|
+
fi
|
|
1442
|
+
crew_dispatch_validate
|
|
1443
|
+
if [ "${FM_BOOTSTRAP_VERBOSE_FACTS:-0}" = 1 ] \
|
|
1444
|
+
&& ! fm_backlog_backend_manual "$CONFIG" && fm_tasks_axi_compatible; then
|
|
1445
|
+
echo "BOOTSTRAP_INFO: tasks-axi available"
|
|
1446
|
+
fi
|
|
1447
|
+
detect_home_summary_publication
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
# This home's ledger publication is deliberately best-effort: every lifecycle
|
|
1451
|
+
# trigger calls it with --best-effort so a failure can never change the result
|
|
1452
|
+
# of a session start, a spawn, a teardown, or a watcher poll. That is correct,
|
|
1453
|
+
# and it also means a home that never manages to publish says nothing at all -
|
|
1454
|
+
# the failures land only in the bounded home-local record nobody reads.
|
|
1455
|
+
#
|
|
1456
|
+
# So read that same record here, where a session start already looks, and say so
|
|
1457
|
+
# once when the evidence is a pattern rather than a blip: the ledger has not
|
|
1458
|
+
# been (re)published, and at least FM_HOME_SUMMARY_FAILURE_REPORT attempts have
|
|
1459
|
+
# failed since whenever it last was. No new record, no new state, no retry
|
|
1460
|
+
# policy - just the existing evidence, surfaced.
|
|
1461
|
+
detect_home_summary_publication() {
|
|
1462
|
+
local log="$STATE/.home-summary-refresh.log" ledger="$STATE/home-summary.json"
|
|
1463
|
+
local since='' counted failures last threshold
|
|
1464
|
+
threshold=${FM_HOME_SUMMARY_FAILURE_REPORT:-2}
|
|
1465
|
+
case "$threshold" in ''|*[!0-9]*|0) threshold=2 ;; esac
|
|
1466
|
+
[ -f "$log" ] && [ -r "$log" ] && [ ! -L "$log" ] || return 0
|
|
1467
|
+
if [ -f "$ledger" ] && [ -r "$ledger" ] && [ ! -L "$ledger" ]; then
|
|
1468
|
+
since=$(LC_ALL=C sed -n 's/.*"generated"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
1469
|
+
"$ledger" 2>/dev/null | head -1)
|
|
1470
|
+
fi
|
|
1471
|
+
# Publication and failure stamps have whole-second precision, so failures in
|
|
1472
|
+
# the publication's own second remain quiet until a later failure advances
|
|
1473
|
+
# the record. That bounded delay avoids a precision dependency in bootstrap.
|
|
1474
|
+
counted=$(LC_ALL=C awk -v since="$since" '
|
|
1475
|
+
match($0, /^\[[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z\]/) {
|
|
1476
|
+
stamp = substr($0, 2, RLENGTH - 2)
|
|
1477
|
+
if (since == "" || stamp > since) {
|
|
1478
|
+
n += 1
|
|
1479
|
+
last = substr($0, RLENGTH + 2)
|
|
1480
|
+
} else if (stamp == since) {
|
|
1481
|
+
same_second += 1
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
END {
|
|
1485
|
+
if (since != "" && n > 0) n += same_second
|
|
1486
|
+
printf "%d\t%s", n + 0, last
|
|
1487
|
+
}' "$log" 2>/dev/null) || return 0
|
|
1488
|
+
failures=${counted%%$'\t'*}
|
|
1489
|
+
last=${counted#*$'\t'}
|
|
1490
|
+
case "$failures" in ''|*[!0-9]*) return 0 ;; esac
|
|
1491
|
+
[ "$failures" -ge "$threshold" ] || return 0
|
|
1492
|
+
last=$(printf '%s' "$last" | cut -c1-200)
|
|
1493
|
+
if [ -z "$since" ]; then
|
|
1494
|
+
echo "HOME_SUMMARY: this home has never published state/home-summary.json; $failures failed attempt(s) recorded in state/.home-summary-refresh.log, last: $last"
|
|
1495
|
+
else
|
|
1496
|
+
echo "HOME_SUMMARY: state/home-summary.json has not been republished since $since; $failures failed attempt(s) recorded in state/.home-summary-refresh.log, last: $last"
|
|
1497
|
+
fi
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
# The order below is the order the diagnostics have always printed in, so a
|
|
1501
|
+
# `skip` run is the same output with the network lines removed rather than a
|
|
1502
|
+
# reshuffle. `gh auth status` sits between the two local blocks because that is
|
|
1503
|
+
# where it has always been.
|
|
1504
|
+
# Each network owner below is bracketed by an elapsed-time record, so a deferred
|
|
1505
|
+
# stage that ran long can be attributed to the phase that spent the time.
|
|
1506
|
+
# fm-timing-lib.sh discards the record unless the caller asked for timings, and
|
|
1507
|
+
# every sweep is still called directly. Per-secondmate remote probes run
|
|
1508
|
+
# concurrently; clone refresh overlaps them. Diagnostic lines are replayed in
|
|
1509
|
+
# original order so attribution is unchanged.
|
|
1510
|
+
# The stamp variable is named for the library rather than `start` on purpose:
|
|
1511
|
+
# fleet_sync and others assign plain names like `start` without `local`, and
|
|
1512
|
+
# bash's dynamic scoping would let them overwrite a stamp held by a caller.
|
|
1513
|
+
local_phase && detect_local_tools
|
|
1514
|
+
if network_phase; then
|
|
1515
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
1516
|
+
gh auth status >/dev/null 2>&1 || echo "NEEDS_GH_AUTH"
|
|
1517
|
+
fm_timing_record phase gh-auth "$__fm_timing_stamp"
|
|
1518
|
+
fi
|
|
1519
|
+
local_phase && detect_local_config
|
|
1520
|
+
|
|
1521
|
+
if [ "${FM_BOOTSTRAP_DETECT_ONLY:-0}" != 1 ]; then
|
|
1522
|
+
# secondmate_sync consumes SECONDMATE_RESPAWNED_IDS from the liveness sweep, so
|
|
1523
|
+
# those two always run together in the same phase. Clone refresh does not
|
|
1524
|
+
# depend on them, so it starts in the background and overlaps their wall clock.
|
|
1525
|
+
fleet_sync_pid=
|
|
1526
|
+
fleet_sync_out=
|
|
1527
|
+
if network_phase && network_sweep_authorized 'project clone refresh'; then
|
|
1528
|
+
fleet_sync_out=$(mktemp "${TMPDIR:-/tmp}/fm-bootstrap-fleet.XXXXXX") || fleet_sync_out=
|
|
1529
|
+
if [ -n "$fleet_sync_out" ]; then
|
|
1530
|
+
(
|
|
1531
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
1532
|
+
fleet_sync
|
|
1533
|
+
fm_timing_record phase fleet-sync "$__fm_timing_stamp"
|
|
1534
|
+
) >"$fleet_sync_out" 2>&1 &
|
|
1535
|
+
fleet_sync_pid=$!
|
|
1536
|
+
else
|
|
1537
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
1538
|
+
fleet_sync
|
|
1539
|
+
fm_timing_record phase fleet-sync "$__fm_timing_stamp"
|
|
1540
|
+
fi
|
|
1541
|
+
fi
|
|
1542
|
+
if network_phase; then
|
|
1543
|
+
if network_sweep_authorized 'dead-secondmate relaunch'; then
|
|
1544
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
1545
|
+
secondmate_liveness_sweep
|
|
1546
|
+
fm_timing_record phase secondmate-liveness "$__fm_timing_stamp"
|
|
1547
|
+
fi
|
|
1548
|
+
if network_sweep_authorized 'secondmate convergence'; then
|
|
1549
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
1550
|
+
secondmate_sync
|
|
1551
|
+
fm_timing_record phase secondmate-sync "$__fm_timing_stamp"
|
|
1552
|
+
fi
|
|
1553
|
+
if network_sweep_authorized 'pending handoff delivery'; then
|
|
1554
|
+
__fm_timing_stamp=$(fm_timing_now_ms)
|
|
1555
|
+
secondmate_handoff_resume
|
|
1556
|
+
fm_timing_record phase handoff-delivery "$__fm_timing_stamp"
|
|
1557
|
+
fi
|
|
1558
|
+
fi
|
|
1559
|
+
# x_mode_setup writes local Relay artifacts only and never leaves the machine.
|
|
1560
|
+
local_phase && x_mode_setup
|
|
1561
|
+
if [ -n "$fleet_sync_pid" ]; then
|
|
1562
|
+
wait "$fleet_sync_pid" || true
|
|
1563
|
+
cat "$fleet_sync_out"
|
|
1564
|
+
rm -f "$fleet_sync_out"
|
|
1565
|
+
fi
|
|
1566
|
+
fi
|
|
1567
|
+
local_phase && secondmate_handoff_detect
|
|
1568
|
+
exit 0
|