@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,1990 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Behavior tests for the bearings projection wrapper over fm-fleet-snapshot.sh.
|
|
3
|
+
# Covers the output/token bound, TOON/JSON parity, the local-only default (zero
|
|
4
|
+
# GitHub/network calls), the --include-prs opt-in path, graceful degradation on a
|
|
5
|
+
# partial PR-fetch failure, end-to-end unresolved-decision durability, and current
|
|
6
|
+
# report pointers.
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
# shellcheck source=tests/lib.sh
|
|
10
|
+
# shellcheck disable=SC1091
|
|
11
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
12
|
+
|
|
13
|
+
BEARINGS="$ROOT/bin/fm-bearings-snapshot.sh"
|
|
14
|
+
TMP_ROOT=$(fm_test_tmproot fm-bearings)
|
|
15
|
+
# Keep disposable homes outside the snapshot's fixture repo boundary even when
|
|
16
|
+
# TMPDIR is inside an isolated source worktree.
|
|
17
|
+
FM_ROOT_OVERRIDE="$TMP_ROOT/fixture-root"
|
|
18
|
+
mkdir -p "$FM_ROOT_OVERRIDE"
|
|
19
|
+
export FM_ROOT_OVERRIDE
|
|
20
|
+
|
|
21
|
+
command -v jq >/dev/null 2>&1 || { echo "skip: jq not found"; exit 0; }
|
|
22
|
+
|
|
23
|
+
# A fakebin that stubs the local tools the canonical snapshot may reach for, plus a
|
|
24
|
+
# gh/gh-axi that RECORDS every call to $NET_LOG so a test can prove the default path
|
|
25
|
+
# makes no network call. gh returns one fixture open PR keyed to the ship task.
|
|
26
|
+
make_fakebin() { # <dir>
|
|
27
|
+
local fb
|
|
28
|
+
fb=$(fm_fakebin "$1")
|
|
29
|
+
cat > "$fb/no-mistakes" <<'SH'
|
|
30
|
+
#!/usr/bin/env bash
|
|
31
|
+
[ "${FAKE_NM_SLEEP:-0}" = 1 ] && sleep 30
|
|
32
|
+
exit 0
|
|
33
|
+
SH
|
|
34
|
+
cat > "$fb/tmux" <<'SH'
|
|
35
|
+
#!/usr/bin/env bash
|
|
36
|
+
case "${1:-}" in
|
|
37
|
+
display-message) case "$*" in *dead-*) exit 1 ;; *) printf '%%1\n' ;; esac ;;
|
|
38
|
+
capture-pane)
|
|
39
|
+
case "$*" in
|
|
40
|
+
*fm-domain-alpha*) printf 'stale terminal summary: Phase 7 started\n> \n' ;;
|
|
41
|
+
*) printf 'all quiet\n> \n' ;;
|
|
42
|
+
esac
|
|
43
|
+
;;
|
|
44
|
+
esac
|
|
45
|
+
exit 0
|
|
46
|
+
SH
|
|
47
|
+
cat > "$fb/gh" <<'SH'
|
|
48
|
+
#!/usr/bin/env bash
|
|
49
|
+
echo "gh $*" >> "$NET_LOG"
|
|
50
|
+
if [ "${FAKE_GH_FAIL:-0}" = 1 ]; then exit 1; fi
|
|
51
|
+
if [ "${FAKE_GH_SLEEP:-0}" = 1 ]; then sleep 30; fi
|
|
52
|
+
if [ "${FAKE_GH_MANY:-0}" = 1 ]; then
|
|
53
|
+
cat <<'JSON'
|
|
54
|
+
[{"number":1,"title":"One","url":"https://github.com/acme/repo/pull/1","headRefName":"fm/one","reviewDecision":"","mergeable":"MERGEABLE","statusCheckRollup":[]},{"number":2,"title":"Two","url":"https://github.com/acme/repo/pull/2","headRefName":"fm/two","reviewDecision":"","mergeable":"MERGEABLE","statusCheckRollup":[]},{"number":3,"title":"Three","url":"https://github.com/acme/repo/pull/3","headRefName":"fm/three","reviewDecision":"","mergeable":"MERGEABLE","statusCheckRollup":[]}]
|
|
55
|
+
JSON
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
cat <<'JSON'
|
|
59
|
+
[{"number":9,"title":"Ship the thing","url":"https://github.com/kunchenguid/firstmate/pull/9","headRefName":"fm/ship-task","reviewDecision":"APPROVED","mergeable":"MERGEABLE","statusCheckRollup":[{"conclusion":"SUCCESS","status":"COMPLETED"}]}]
|
|
60
|
+
JSON
|
|
61
|
+
SH
|
|
62
|
+
cat > "$fb/gh-axi" <<'SH'
|
|
63
|
+
#!/usr/bin/env bash
|
|
64
|
+
echo "gh-axi $*" >> "$NET_LOG"
|
|
65
|
+
[ "${FAKE_GH_FAIL:-0}" = 1 ] && exit 1
|
|
66
|
+
exit 0
|
|
67
|
+
SH
|
|
68
|
+
cat > "$fb/curl" <<'SH'
|
|
69
|
+
#!/usr/bin/env bash
|
|
70
|
+
echo "curl $*" >> "$NET_LOG"
|
|
71
|
+
exit 1
|
|
72
|
+
SH
|
|
73
|
+
chmod +x "$fb/no-mistakes" "$fb/tmux" "$fb/gh" "$fb/gh-axi" "$fb/curl"
|
|
74
|
+
printf '%s\n' "$fb"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
make_home() { # <name>
|
|
78
|
+
local home=$TMP_ROOT/$1
|
|
79
|
+
mkdir -p "$home/state" "$home/data" "$home/projects" "$home/config"
|
|
80
|
+
printf '%s\n' "$home"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
record_claude_state() { # <state-dir> <id> <busy|idle>
|
|
84
|
+
local state=$1 id=$2 semantic_state=$3 gen event
|
|
85
|
+
case "$semantic_state" in
|
|
86
|
+
busy) event=user-prompt-submit ;;
|
|
87
|
+
idle) event=stop ;;
|
|
88
|
+
*) fail "unsupported semantic fixture state: $semantic_state" ;;
|
|
89
|
+
esac
|
|
90
|
+
gen=$("$ROOT/bin/fm-busy-event.sh" arm "$state" "$id")
|
|
91
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$state" "$id" "$semantic_state" --gen "$gen" \
|
|
92
|
+
--source claude-hook --event "$event"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fixture_mate_home() { # <parent-home>
|
|
96
|
+
printf '%s/%s-secondmate-home\n' "$TMP_ROOT" "$(basename "$1")"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Standard fixture: a ship task with a recorded PR, a scout task with a report, a
|
|
100
|
+
# secondmate with a MASKED open decision (needs-decision then a later unrelated
|
|
101
|
+
# done), and a backlog with a superseded queued item.
|
|
102
|
+
write_fixture() { # <home>
|
|
103
|
+
local home=$1 mate
|
|
104
|
+
mate=$(fixture_mate_home "$home")
|
|
105
|
+
mkdir -p "$home/projects/ship-wt" "$home/data/scout-x" "$mate/data" "$mate/state" "$mate/config" "$mate/projects" "$mate/bin"
|
|
106
|
+
printf '# Firstmate fixture\n' > "$mate/AGENTS.md"
|
|
107
|
+
printf 'mate\n' > "$mate/.fm-secondmate-home"
|
|
108
|
+
printf -- '- mate - fixture domain (home: %s; scope: fixture work; projects: firstmate; added 2026-07-11)\n' \
|
|
109
|
+
"$mate" > "$home/data/secondmates.md"
|
|
110
|
+
cat > "$home/data/backlog.md" <<EOF
|
|
111
|
+
## In flight
|
|
112
|
+
- [ ] ship-task - Ship the thing (repo: firstmate) (kind: ship) (since 2026-07-11)
|
|
113
|
+
- [ ] scout-x - Investigate the thing data/scout-x/report.md (repo: firstmate) (kind: scout) (since 2026-07-11)
|
|
114
|
+
|
|
115
|
+
## Queued
|
|
116
|
+
- [ ] live-gate - Real queued work blocked-by: ship-task (repo: firstmate) (kind: ship)
|
|
117
|
+
- [ ] dead-gate - Old conditional work (repo: firstmate) (kind: scout)
|
|
118
|
+
NOT REQUIRED - superseded 2026-07-11; kept as reference only.
|
|
119
|
+
|
|
120
|
+
## Done
|
|
121
|
+
- [x] done-a - Landed thing https://github.com/kunchenguid/firstmate/pull/7 (repo: firstmate) (kind: ship) (merged 2026-07-10)
|
|
122
|
+
EOF
|
|
123
|
+
printf '# Scout X\n' > "$home/data/scout-x/report.md"
|
|
124
|
+
fm_write_meta "$home/state/ship-task.meta" \
|
|
125
|
+
"window=firstmate:fm-ship-task" \
|
|
126
|
+
"worktree=$home/projects/ship-wt" \
|
|
127
|
+
"project=firstmate" \
|
|
128
|
+
"harness=claude" \
|
|
129
|
+
"kind=ship" \
|
|
130
|
+
"mode=no-mistakes" \
|
|
131
|
+
"pr=https://github.com/kunchenguid/firstmate/pull/9"
|
|
132
|
+
record_claude_state "$home/state" ship-task busy
|
|
133
|
+
printf 'working: building the thing\n' > "$home/state/ship-task.status"
|
|
134
|
+
fm_write_meta "$home/state/scout-x.meta" \
|
|
135
|
+
"window=firstmate:fm-scout-x" \
|
|
136
|
+
"worktree=$home/projects/ship-wt" \
|
|
137
|
+
"project=firstmate" \
|
|
138
|
+
"harness=claude" \
|
|
139
|
+
"kind=scout" \
|
|
140
|
+
"mode=scout"
|
|
141
|
+
record_claude_state "$home/state" scout-x idle
|
|
142
|
+
printf 'done: report ready\n' > "$home/state/scout-x.status"
|
|
143
|
+
fm_write_meta "$home/state/mate.meta" \
|
|
144
|
+
"window=firstmate:fm-mate" \
|
|
145
|
+
"worktree=$mate" \
|
|
146
|
+
"project=$mate" \
|
|
147
|
+
"harness=codex" \
|
|
148
|
+
"kind=secondmate" \
|
|
149
|
+
"mode=secondmate" \
|
|
150
|
+
"home=$mate" \
|
|
151
|
+
"projects=firstmate"
|
|
152
|
+
printf 'needs-decision [key=race]: pick subscribe order\n' > "$home/state/mate.status"
|
|
153
|
+
printf 'done: an unrelated subtask finished\n' >> "$home/state/mate.status"
|
|
154
|
+
fm_write_meta "$home/state/external-wait.meta" \
|
|
155
|
+
"window=firstmate:fm-external-wait" \
|
|
156
|
+
"worktree=$home/projects/ship-wt" \
|
|
157
|
+
"project=firstmate" \
|
|
158
|
+
"harness=claude" \
|
|
159
|
+
"kind=ship" \
|
|
160
|
+
"mode=no-mistakes"
|
|
161
|
+
record_claude_state "$home/state" external-wait idle
|
|
162
|
+
printf 'paused: declared external-wait for upstream release\n' > "$home/state/external-wait.status"
|
|
163
|
+
# The secondmate's OWN home backlog records a merge it managed. This lands in the
|
|
164
|
+
# secondmate home, never the main backlog, so landed-work views only see it via the
|
|
165
|
+
# bounded cross-home Done roll-up.
|
|
166
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
167
|
+
## In flight
|
|
168
|
+
- [ ] mate - Decide subscription order (repo: firstmate) (kind: ship) (since 2026-07-11)
|
|
169
|
+
|
|
170
|
+
## Queued
|
|
171
|
+
- [ ] mate-decision-race - Choose subscription order (repo: firstmate) (kind: captain) (hold: captain choice pending) (hold-kind: captain)
|
|
172
|
+
|
|
173
|
+
## Done
|
|
174
|
+
- [x] mate-landed - Secondmate-managed fix https://github.com/kunchenguid/firstmate/pull/50 (repo: firstmate) (kind: ship) (merged 2026-07-11)
|
|
175
|
+
EOF
|
|
176
|
+
mkdir -p "$mate/projects/mate"
|
|
177
|
+
fm_write_meta "$mate/state/mate.meta" \
|
|
178
|
+
"window=firstmate:fm-mate" "worktree=$mate/projects/mate" "project=firstmate" \
|
|
179
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
180
|
+
record_claude_state "$mate/state" mate idle
|
|
181
|
+
printf 'needs-decision [key=race]: pick subscribe order\n' > "$mate/state/mate.status"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
run() { # <home> <fakebin> <args...>
|
|
185
|
+
local home=$1 fakebin=$2; shift 2
|
|
186
|
+
PATH="$fakebin:$PATH" FM_HOME="$home" FM_BEARINGS_NOW=2026-07-11T18:00:00Z NET_LOG="$home/net.log" "$BEARINGS" "$@"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# End-to-end Domain Alpha regression fixture.
|
|
190
|
+
# The parent event claims Phase 7 started, while the registered home has no child
|
|
191
|
+
# metadata, every sample-rollout item is Done, and only an external legal hold remains.
|
|
192
|
+
write_domain_alpha_fixture() { # <parent-home> <secondmate-home>
|
|
193
|
+
local home=$1 mate=$2 i
|
|
194
|
+
mkdir -p "$mate/state" "$mate/data" "$mate/config" "$mate/projects" "$mate/bin"
|
|
195
|
+
printf '# Firstmate fixture\n' > "$mate/AGENTS.md"
|
|
196
|
+
printf 'domain-alpha\n' > "$mate/.fm-secondmate-home"
|
|
197
|
+
printf -- '- domain-alpha - sample rollout (home: %s; scope: sample rollout and legal release; projects: sample; added 2026-07-13)\n' \
|
|
198
|
+
"$mate" > "$home/data/secondmates.md"
|
|
199
|
+
fm_write_secondmate_meta "$home/state/domain-alpha.meta" "$mate" "firstmate:fm-domain-alpha" sample
|
|
200
|
+
printf 'working [key=phase7]: Phase 7 started\n' > "$home/state/domain-alpha.status"
|
|
201
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
202
|
+
## In flight
|
|
203
|
+
|
|
204
|
+
## Queued
|
|
205
|
+
- [ ] legal-release - Release approval blocked-by: external-legal - external legal dependency (repo: sample) (kind: ship)
|
|
206
|
+
|
|
207
|
+
## Done
|
|
208
|
+
EOF
|
|
209
|
+
i=1
|
|
210
|
+
while [ "$i" -le 7 ]; do
|
|
211
|
+
printf -- '- [x] phase%s - Sample rollout Phase %s (repo: sample) (kind: ship) (done 2026-07-%02d)\n' \
|
|
212
|
+
"$i" "$i" "$i" >> "$mate/data/backlog.md"
|
|
213
|
+
i=$((i + 1))
|
|
214
|
+
done
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# This is the Domain Alpha failure shape exactly: the structured home says Phase 7 is Done
|
|
218
|
+
# and no child is active, so the stale parent event must never become Underway.
|
|
219
|
+
test_domain_alpha_stale_parent_event_does_not_become_current_work() {
|
|
220
|
+
local home mate fakebin json canonical
|
|
221
|
+
home=$(make_home domain-alpha-parent)
|
|
222
|
+
mate="$TMP_ROOT/domain-alpha-home"
|
|
223
|
+
write_domain_alpha_fixture "$home" "$mate"
|
|
224
|
+
fakebin=$(make_fakebin "$home"); : > "$home/net.log"
|
|
225
|
+
json=$(FAKE_GH_FAIL=1 run "$home" "$fakebin" --json)
|
|
226
|
+
printf '%s' "$json" | jq -e '
|
|
227
|
+
(.in_flight | any(.[]; .id == "domain-alpha") | not)
|
|
228
|
+
and (.secondmates | any(.[];
|
|
229
|
+
.id == "domain-alpha"
|
|
230
|
+
and .state == "externally_held"
|
|
231
|
+
and .provenance == "structured-home"
|
|
232
|
+
and .freshness == "fresh"
|
|
233
|
+
and .contradiction == true))
|
|
234
|
+
and (.gates | any(.[]; .id == "legal-release" and .owner == "domain-alpha"))
|
|
235
|
+
and (.landed | any(.[]; .id == "phase7" and .owner == "domain-alpha"))
|
|
236
|
+
' >/dev/null || fail "stale parent Phase 7 event overrode authoritative Domain Alpha state: $json"
|
|
237
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
238
|
+
FM_SNAPSHOT_NOW_EPOCH=1783792800 FM_SNAPSHOT_TERMINAL_LINES=2 FM_SNAPSHOT_TERMINAL_BYTES=64 \
|
|
239
|
+
NET_LOG="$home/net.log" FAKE_GH_FAIL=1 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
240
|
+
printf '%s' "$canonical" | jq -e '
|
|
241
|
+
.secondmate_current.records[] | select(.id == "domain-alpha")
|
|
242
|
+
| .provenance.selected == "structured-home"
|
|
243
|
+
and .freshness.status == "fresh"
|
|
244
|
+
and .terminal_evidence.provenance == "parent-direct-report-terminal"
|
|
245
|
+
and .terminal_evidence.trust == "untrusted-supplement"
|
|
246
|
+
and .terminal_evidence.captured == true
|
|
247
|
+
and .terminal_evidence.lines == 2
|
|
248
|
+
and .terminal_evidence.bytes <= 64
|
|
249
|
+
and (.terminal_evidence | has("content") | not)
|
|
250
|
+
and .terminal_evidence.event_note_seen == true
|
|
251
|
+
and .terminal_evidence.contradiction == true
|
|
252
|
+
and .contradiction == true
|
|
253
|
+
' >/dev/null || fail "bounded terminal contradiction evidence was not labeled and subordinate: $canonical"
|
|
254
|
+
[ ! -s "$home/net.log" ] || fail "Domain Alpha structured-home read made a network call: $(cat "$home/net.log")"
|
|
255
|
+
pass "Domain Alpha structured state overrides a stale parent Phase 7 event"
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
test_gnu_stat_uses_file_formats_without_bsd_fallback_pollution() {
|
|
259
|
+
local home mate fakebin canonical stat_log
|
|
260
|
+
home=$(make_home gnu-stat-parent)
|
|
261
|
+
mate="$TMP_ROOT/gnu-stat-home"
|
|
262
|
+
write_domain_alpha_fixture "$home" "$mate"
|
|
263
|
+
fakebin=$(make_fakebin "$home")
|
|
264
|
+
stat_log="$home/stat.log"
|
|
265
|
+
cat > "$fakebin/uname" <<'SH'
|
|
266
|
+
#!/usr/bin/env bash
|
|
267
|
+
printf 'Linux\n'
|
|
268
|
+
SH
|
|
269
|
+
cat > "$fakebin/stat" <<'SH'
|
|
270
|
+
#!/usr/bin/env bash
|
|
271
|
+
printf '%s\n' "$*" >> "$STAT_LOG"
|
|
272
|
+
case "$1 $2" in
|
|
273
|
+
'-c %a') printf '600\n' ;;
|
|
274
|
+
'-c %Y') printf '1783792800\n' ;;
|
|
275
|
+
'-c %s') LC_ALL=C wc -c < "$3" | tr -d ' ' ;;
|
|
276
|
+
-f\ *)
|
|
277
|
+
printf ' File: "%s"\nBlocks: Total: 1\n' "$2"
|
|
278
|
+
exit 1
|
|
279
|
+
;;
|
|
280
|
+
*) exit 2 ;;
|
|
281
|
+
esac
|
|
282
|
+
SH
|
|
283
|
+
chmod +x "$fakebin/uname" "$fakebin/stat"
|
|
284
|
+
canonical=$(PATH="$fakebin:$PATH" STAT_LOG="$stat_log" FM_HOME="$home" \
|
|
285
|
+
FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z FM_SNAPSHOT_NOW_EPOCH=1783792800 \
|
|
286
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
287
|
+
printf '%s' "$canonical" | jq -e '
|
|
288
|
+
.secondmate_current.records[] | select(.id == "domain-alpha")
|
|
289
|
+
| .provenance.selected == "structured-home"
|
|
290
|
+
and .parent_event.activity_scan.available == true
|
|
291
|
+
' >/dev/null || fail "GNU stat fixture corrupted the authoritative secondmate summary: $canonical"
|
|
292
|
+
assert_contains "$(cat "$stat_log")" '-c %a' "GNU registry mode must use stat -c"
|
|
293
|
+
assert_contains "$(cat "$stat_log")" '-c %Y' "GNU parent-event mtime must use stat -c"
|
|
294
|
+
assert_contains "$(cat "$stat_log")" '-c %s' "GNU parent-event size must use stat -c"
|
|
295
|
+
if grep -q '^-f ' "$stat_log"; then
|
|
296
|
+
fail "GNU snapshot invoked BSD stat -f before its GNU file reads: $(cat "$stat_log")"
|
|
297
|
+
fi
|
|
298
|
+
pass "GNU stat file reads select -c without BSD filesystem-report pollution"
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
test_parent_activity_evidence_is_bounded_and_disclosed() {
|
|
302
|
+
local home mate fakebin canonical json i
|
|
303
|
+
home=$(make_home bounded-parent-activity)
|
|
304
|
+
mate="$TMP_ROOT/bounded-parent-activity-home"
|
|
305
|
+
write_domain_alpha_fixture "$home" "$mate"
|
|
306
|
+
: > "$home/state/domain-alpha.status"
|
|
307
|
+
i=1
|
|
308
|
+
while [ "$i" -le 6 ]; do
|
|
309
|
+
printf 'working [key=phase%s]: Phase %s started\n' "$i" "$i" >> "$home/state/domain-alpha.status"
|
|
310
|
+
i=$((i + 1))
|
|
311
|
+
done
|
|
312
|
+
fakebin=$(make_fakebin "$home")
|
|
313
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
314
|
+
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=4 FM_SNAPSHOT_PARENT_ACTIVITY_BYTES=4096 \
|
|
315
|
+
FM_SNAPSHOT_PARENT_ACTIVITIES=2 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
316
|
+
printf '%s' "$canonical" | jq -e '
|
|
317
|
+
.secondmate_current.records[] | select(.id == "domain-alpha")
|
|
318
|
+
| .parent_event.activity_scan.available == true
|
|
319
|
+
and .parent_event.activity_scan.input_truncated == true
|
|
320
|
+
and .parent_event.activity_scan.retained_truncated == true
|
|
321
|
+
and .parent_event.activity_scan.lines_in_window == 4
|
|
322
|
+
and .parent_event.activity_scan.records_in_window == 4
|
|
323
|
+
and .parent_event.activity_scan.reasons == ["line_limit", "activity_limit"]
|
|
324
|
+
and (.parent_event.open_activities | map(.key)) == ["phase5", "phase6"]
|
|
325
|
+
' >/dev/null || fail "parent activity evidence was not bounded and disclosed: $canonical"
|
|
326
|
+
json=$(FM_SNAPSHOT_PARENT_ACTIVITY_LINES=4 FM_SNAPSHOT_PARENT_ACTIVITY_BYTES=4096 \
|
|
327
|
+
FM_SNAPSHOT_PARENT_ACTIVITIES=2 run "$home" "$fakebin" --json)
|
|
328
|
+
printf '%s' "$json" | jq -e '
|
|
329
|
+
.omitted | any(.surface == "secondmate parent activity evidence truncated for 1 record(s)")
|
|
330
|
+
' >/dev/null || fail "bearings did not disclose bounded parent activity evidence: $json"
|
|
331
|
+
pass "parent activity evidence is bounded and disclosed"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
test_active_child_overrides_old_parent_event() {
|
|
335
|
+
local home mate fakebin json canonical
|
|
336
|
+
home=$(make_home active-child-parent)
|
|
337
|
+
mate="$TMP_ROOT/active-child-home"
|
|
338
|
+
write_domain_alpha_fixture "$home" "$mate"
|
|
339
|
+
mkdir -p "$mate/projects/phase8"
|
|
340
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
341
|
+
## In flight
|
|
342
|
+
- [ ] phase8 - Sample rollout Phase 8 (repo: sample) (kind: ship) (since 2026-07-13)
|
|
343
|
+
|
|
344
|
+
## Queued
|
|
345
|
+
|
|
346
|
+
## Done
|
|
347
|
+
- [x] phase7 - Sample rollout Phase 7 (repo: sample) (kind: ship) (done 2026-07-12)
|
|
348
|
+
EOF
|
|
349
|
+
fm_write_meta "$mate/state/phase8.meta" \
|
|
350
|
+
"window=firstmate:fm-phase8" "worktree=$mate/projects/phase8" "project=sample" \
|
|
351
|
+
"harness=codex" "kind=ship" "mode=no-mistakes"
|
|
352
|
+
printf 'working [key=phase8]: implementing Phase 8 parity\nneeds-decision [key=release]: choose release A or B\n' \
|
|
353
|
+
> "$mate/state/phase8.status"
|
|
354
|
+
fakebin=$(make_fakebin "$home")
|
|
355
|
+
json=$(run "$home" "$fakebin" --json)
|
|
356
|
+
printf '%s' "$json" | jq -e '
|
|
357
|
+
(.secondmates | any(.[]; .id == "domain-alpha" and .state != "captain_decision"
|
|
358
|
+
and (.doing | contains("release A or B") | not)))
|
|
359
|
+
and (.decisions_open | any(.owner == "domain-alpha") | not)
|
|
360
|
+
' >/dev/null || fail "status-only child decision leaked into Bearings: $json"
|
|
361
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
362
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
363
|
+
printf '%s' "$canonical" | jq -e '
|
|
364
|
+
.secondmate_current.records[] | select(.id == "domain-alpha") | .endpoints[] | select(.id == "phase8")
|
|
365
|
+
| .endpoint.status == "unknown"
|
|
366
|
+
and .endpoint.exists == true
|
|
367
|
+
and .endpoint.freshness == "fresh"
|
|
368
|
+
and .endpoint.observed_at == "2026-07-11T18:00:00Z"
|
|
369
|
+
' >/dev/null || fail "child endpoint observation lacked bounded current freshness: $canonical"
|
|
370
|
+
pass "Bearings excludes a status-only child decision"
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
test_structured_child_decision_reaches_captains_call() {
|
|
374
|
+
local home mate fakebin json
|
|
375
|
+
home=$(make_home child-decision-parent)
|
|
376
|
+
mate="$TMP_ROOT/child-decision-home"
|
|
377
|
+
write_domain_alpha_fixture "$home" "$mate"
|
|
378
|
+
mkdir -p "$mate/projects/phase8"
|
|
379
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
380
|
+
## In flight
|
|
381
|
+
- [ ] phase8 - Sample rollout Phase 8 (repo: sample) (kind: ship) (since 2026-07-13)
|
|
382
|
+
|
|
383
|
+
## Queued
|
|
384
|
+
- [ ] phase8-decision-release - Choose sample release (repo: sample) (kind: captain) (hold: captain release choice pending) (hold-kind: captain)
|
|
385
|
+
|
|
386
|
+
## Done
|
|
387
|
+
- [x] phase7 - Sample rollout Phase 7 (repo: sample) (kind: ship) (done 2026-07-12)
|
|
388
|
+
EOF
|
|
389
|
+
fm_write_meta "$mate/state/phase8.meta" \
|
|
390
|
+
"window=firstmate:fm-phase8" "worktree=$mate/projects/phase8" "project=sample" \
|
|
391
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
392
|
+
record_claude_state "$mate/state" phase8 idle
|
|
393
|
+
printf 'needs-decision [key=release]: choose release A or B\n' > "$mate/state/phase8.status"
|
|
394
|
+
fakebin=$(make_fakebin "$home")
|
|
395
|
+
json=$(run "$home" "$fakebin" --json)
|
|
396
|
+
printf '%s' "$json" | jq -e '
|
|
397
|
+
(.secondmates | any(.[]; .id == "domain-alpha" and .state == "captain_decision"))
|
|
398
|
+
and (.decisions_open | any(.[]; .id == "domain-alpha/phase8-decision-release"
|
|
399
|
+
and .key == "phase8-decision-release" and .verb == "captain-hold"))
|
|
400
|
+
and (.in_flight | any(.[]; .id == "domain-alpha") | not)
|
|
401
|
+
' >/dev/null || fail "structured child captain hold did not reach Captain Call: $json"
|
|
402
|
+
pass "a structured child captain hold reaches Captain's Call"
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
make_valid_secondmate_home() { # <id> <home>
|
|
406
|
+
local id=$1 home=$2
|
|
407
|
+
mkdir -p "$home/state" "$home/data" "$home/config" "$home/projects" "$home/bin"
|
|
408
|
+
printf '# Firstmate fixture\n' > "$home/AGENTS.md"
|
|
409
|
+
printf '%s\n' "$id" > "$home/.fm-secondmate-home"
|
|
410
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
411
|
+
## In flight
|
|
412
|
+
|
|
413
|
+
## Queued
|
|
414
|
+
|
|
415
|
+
## Done
|
|
416
|
+
EOF
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
append_secondmate_registry() { # <parent> <id> <home>
|
|
420
|
+
printf -- '- %s - fixture domain (home: %s; scope: fixture; projects: sample; added 2026-07-13)\n' \
|
|
421
|
+
"$2" "$3" >> "$1/data/secondmates.md"
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
append_landed_row() { # <secondmate-home> <id> <title> <date>
|
|
425
|
+
printf -- '- [x] %s - %s (repo: firstmate) (kind: ship) (merged %s)\n' \
|
|
426
|
+
"$2" "$3" "$4" >> "$1/data/backlog.md"
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
make_landed_secondmate() { # <parent> <id>
|
|
430
|
+
local parent=$1 id=$2 mate
|
|
431
|
+
mate="$TMP_ROOT/$(basename "$parent")-$id-home"
|
|
432
|
+
make_valid_secondmate_home "$id" "$mate"
|
|
433
|
+
append_secondmate_registry "$parent" "$id" "$mate"
|
|
434
|
+
printf '%s\n' "$mate"
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
write_parent_secondmate_event() { # <parent> <id> <home> <note>
|
|
438
|
+
fm_write_secondmate_meta "$1/state/$2.meta" "$3" "firstmate:fm-$2" sample
|
|
439
|
+
printf 'working [key=%s]: %s\n' "$2" "$4" > "$1/state/$2.status"
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
test_bad_secondmate_homes_never_revive_parent_work() {
|
|
443
|
+
local home fakebin missing invalid unreadable malformed timedout wt json
|
|
444
|
+
home=$(make_home bad-homes)
|
|
445
|
+
: > "$home/data/secondmates.md"
|
|
446
|
+
missing="$TMP_ROOT/missing-home"
|
|
447
|
+
invalid="$TMP_ROOT/invalid-home"
|
|
448
|
+
unreadable="$TMP_ROOT/unreadable-home"
|
|
449
|
+
malformed="$TMP_ROOT/malformed-home"
|
|
450
|
+
timedout="$TMP_ROOT/timedout-home"
|
|
451
|
+
|
|
452
|
+
append_secondmate_registry "$home" missing "$missing"
|
|
453
|
+
|
|
454
|
+
make_valid_secondmate_home invalid "$invalid"
|
|
455
|
+
printf 'someone-else\n' > "$invalid/.fm-secondmate-home"
|
|
456
|
+
append_secondmate_registry "$home" invalid "$invalid"
|
|
457
|
+
write_parent_secondmate_event "$home" invalid "$invalid" "old invalid work"
|
|
458
|
+
|
|
459
|
+
make_valid_secondmate_home unreadable "$unreadable"
|
|
460
|
+
chmod 000 "$unreadable/data"
|
|
461
|
+
append_secondmate_registry "$home" unreadable "$unreadable"
|
|
462
|
+
write_parent_secondmate_event "$home" unreadable "$unreadable" "old unreadable work"
|
|
463
|
+
|
|
464
|
+
make_valid_secondmate_home malformed "$malformed"
|
|
465
|
+
printf '## In flight\nthis current row is not structured\n' > "$malformed/data/backlog.md"
|
|
466
|
+
append_secondmate_registry "$home" malformed "$malformed"
|
|
467
|
+
write_parent_secondmate_event "$home" malformed "$malformed" "old malformed work"
|
|
468
|
+
|
|
469
|
+
make_valid_secondmate_home timedout "$timedout"
|
|
470
|
+
wt="$timedout/projects/slow"
|
|
471
|
+
fm_git_init_commit "$wt"
|
|
472
|
+
git -C "$wt" checkout -q -b fm/slow
|
|
473
|
+
printf '## In flight\n- [ ] slow - Slow child (repo: sample) (kind: ship) (since 2026-07-13)\n\n## Queued\n\n## Done\n' > "$timedout/data/backlog.md"
|
|
474
|
+
fm_write_meta "$timedout/state/slow.meta" \
|
|
475
|
+
"window=firstmate:fm-slow" "worktree=$wt" "project=sample" \
|
|
476
|
+
"harness=codex" "kind=ship" "mode=no-mistakes"
|
|
477
|
+
append_secondmate_registry "$home" timedout "$timedout"
|
|
478
|
+
write_parent_secondmate_event "$home" timedout "$timedout" "old timed work"
|
|
479
|
+
|
|
480
|
+
fakebin=$(make_fakebin "$home")
|
|
481
|
+
json=$(FAKE_NM_SLEEP=1 FM_SNAPSHOT_SECONDMATE_TIMEOUT=1 run "$home" "$fakebin" --json)
|
|
482
|
+
chmod 700 "$unreadable/data"
|
|
483
|
+
printf '%s' "$json" | jq -e '
|
|
484
|
+
(.secondmates | length) == 5
|
|
485
|
+
and all(.secondmates[]; .state == "unknown")
|
|
486
|
+
and (.in_flight | map(.id) | all(. != "invalid" and . != "unreadable" and . != "malformed" and . != "timedout"))
|
|
487
|
+
and (.secondmates | any(.[]; .id == "missing" and .provenance == "unknown"
|
|
488
|
+
and .freshness == "unknown" and (.reason | contains("invalid home"))))
|
|
489
|
+
and ([.secondmates[] | select(.id != "missing")]
|
|
490
|
+
| all(.provenance == "parent-event-fallback" and .freshness == "historical-event"))
|
|
491
|
+
and (.secondmates | any(.[]; .id == "invalid" and (.reason | contains("marked for"))))
|
|
492
|
+
and (.secondmates | any(.[]; .id == "unreadable" and (.reason | test("invalid home|unreadable"))))
|
|
493
|
+
and (.secondmates | any(.[]; .id == "malformed" and (.reason | contains("unstructured current backlog row"))))
|
|
494
|
+
and (.secondmates | any(.[]; .id == "timedout" and (.reason | contains("timed out"))))
|
|
495
|
+
and ([.secondmate_reconcile[].id] == ["malformed"])
|
|
496
|
+
and (.secondmate_reconcile[0].kind == "unstructured_current")
|
|
497
|
+
' >/dev/null || fail "bad home outcomes revived stale work or lacked provenance: $json"
|
|
498
|
+
pass "missing, invalid, unreadable, malformed, and timed-out homes stay explicit unknowns"
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
test_oversized_secondmate_summary_stays_strict_unknown() {
|
|
502
|
+
local home mate fakebin json i
|
|
503
|
+
home=$(make_home oversized-home)
|
|
504
|
+
mate="$TMP_ROOT/oversized-secondmate-home"
|
|
505
|
+
make_valid_secondmate_home oversized "$mate"
|
|
506
|
+
append_secondmate_registry "$home" oversized "$mate"
|
|
507
|
+
fm_write_secondmate_meta "$home/state/oversized.meta" "$mate" "firstmate:fm-oversized" sample
|
|
508
|
+
printf 'working [key=old]: stale parent activity\n' > "$home/state/oversized.status"
|
|
509
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
510
|
+
## In flight
|
|
511
|
+
|
|
512
|
+
## Queued
|
|
513
|
+
|
|
514
|
+
## Done
|
|
515
|
+
EOF
|
|
516
|
+
i=1
|
|
517
|
+
while [ "$i" -le 30 ]; do
|
|
518
|
+
printf -- '- [x] landed-%02d - Bounded landed fixture %02d (repo: sample) (kind: ship) (done 2026-07-01)\n' \
|
|
519
|
+
"$i" "$i" >> "$mate/data/backlog.md"
|
|
520
|
+
i=$((i + 1))
|
|
521
|
+
done
|
|
522
|
+
fakebin=$(make_fakebin "$home")
|
|
523
|
+
json=$(FM_SNAPSHOT_SECONDMATE_MAX_BYTES=512 run "$home" "$fakebin" --json)
|
|
524
|
+
printf '%s' "$json" | jq -e '
|
|
525
|
+
(.secondmates | any(.id == "oversized" and .state == "unknown"
|
|
526
|
+
and .provenance == "parent-event-fallback"
|
|
527
|
+
and (.reason | contains("exceeded byte limit"))))
|
|
528
|
+
and (.in_flight | any(.id == "oversized") | not)
|
|
529
|
+
and (.decisions_open | any(.owner == "oversized") | not)
|
|
530
|
+
and (.landed | any(.owner == "oversized") | not)
|
|
531
|
+
' >/dev/null || fail "oversized summary revived or retained unvalidated surfaces: $json"
|
|
532
|
+
pass "an oversized secondmate summary retains the strict empty unknown fallback"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
test_secondmate_and_child_bounds_are_disclosed() {
|
|
536
|
+
local home fakebin id mate child json expanded canonical i
|
|
537
|
+
home=$(make_home secondmate-bounds)
|
|
538
|
+
: > "$home/data/secondmates.md"
|
|
539
|
+
for id in a b c; do
|
|
540
|
+
mate="$TMP_ROOT/bounds-$id"
|
|
541
|
+
make_valid_secondmate_home "$id" "$mate"
|
|
542
|
+
append_secondmate_registry "$home" "$id" "$mate"
|
|
543
|
+
done
|
|
544
|
+
mate="$TMP_ROOT/bounds-a"
|
|
545
|
+
: > "$mate/data/backlog.md"
|
|
546
|
+
printf '## In flight\n' >> "$mate/data/backlog.md"
|
|
547
|
+
i=1
|
|
548
|
+
while [ "$i" -le 3 ]; do
|
|
549
|
+
child="child-$i"
|
|
550
|
+
mkdir -p "$mate/projects/$child"
|
|
551
|
+
printf -- '- [ ] %s - Active %s (repo: sample) (kind: ship) (since 2026-07-13)\n' "$child" "$child" >> "$mate/data/backlog.md"
|
|
552
|
+
fm_write_meta "$mate/state/$child.meta" \
|
|
553
|
+
"window=firstmate:fm-$child" "worktree=$mate/projects/$child" "project=sample" \
|
|
554
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
555
|
+
record_claude_state "$mate/state" "$child" busy
|
|
556
|
+
printf 'working [key=%s]: active child %s\n' "$child" "$i" > "$mate/state/$child.status"
|
|
557
|
+
i=$((i + 1))
|
|
558
|
+
done
|
|
559
|
+
printf '\n## Queued\n\n## Done\n' >> "$mate/data/backlog.md"
|
|
560
|
+
fakebin=$(make_fakebin "$home")
|
|
561
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
562
|
+
FM_SNAPSHOT_SECONDMATES=2 FM_SNAPSHOT_SECONDMATE_CHILDREN=2 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
563
|
+
printf '%s' "$canonical" | jq -e '
|
|
564
|
+
.secondmate_current.total_registered == 3
|
|
565
|
+
and .secondmate_current.shown == 2
|
|
566
|
+
and .secondmate_current.truncated == 1
|
|
567
|
+
and (.secondmate_current.records[] | select(.id == "a")
|
|
568
|
+
| .counts.active_children == 3 and (.active_children | length) == 2
|
|
569
|
+
and (.omitted | any(.surface == "active_children" and .count == 1)))
|
|
570
|
+
and (.secondmate_current.records | any(.id == "b" and .current.state == "no_active_work"))
|
|
571
|
+
' >/dev/null || fail "canonical secondmate or child bounds were not enforced: $canonical"
|
|
572
|
+
json=$(FM_SNAPSHOT_SECONDMATES=2 FM_SNAPSHOT_SECONDMATE_CHILDREN=2 FM_BEARINGS_SECONDMATES=1 \
|
|
573
|
+
run "$home" "$fakebin" --json)
|
|
574
|
+
printf '%s' "$json" | jq -e '
|
|
575
|
+
(.secondmates | length) == 1
|
|
576
|
+
and ([.omitted[].surface] | any(test("secondmates showing 1 of 2")))
|
|
577
|
+
and ([.omitted[].surface] | any(test("registered secondmates omitted by snapshot bound: 1")))
|
|
578
|
+
' >/dev/null || fail "bearings secondmate bound was not disclosed: $json"
|
|
579
|
+
expanded=$(FM_SNAPSHOT_SECONDMATE_CHILDREN=2 FM_BEARINGS_SECONDMATES=1 \
|
|
580
|
+
run "$home" "$fakebin" --json --all-secondmates)
|
|
581
|
+
printf '%s' "$expanded" | jq -e '
|
|
582
|
+
(.secondmates | length) == 3
|
|
583
|
+
and ([.omitted[].surface] | any(test("secondmates showing|registered secondmates omitted")) | not)
|
|
584
|
+
' >/dev/null || fail "--all-secondmates did not expand the canonical and bearings bounds: $expanded"
|
|
585
|
+
pass "secondmate and per-home child counts are bounded, disclosed, and explicitly expandable"
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
test_parent_decision_is_untrusted_contradiction_only() {
|
|
589
|
+
local home mate fakebin canonical json
|
|
590
|
+
home=$(make_home parent-decision-only)
|
|
591
|
+
mate="$TMP_ROOT/parent-decision-only-home"
|
|
592
|
+
make_valid_secondmate_home authority "$mate"
|
|
593
|
+
append_secondmate_registry "$home" authority "$mate"
|
|
594
|
+
fm_write_secondmate_meta "$home/state/authority.meta" "$mate" "firstmate:fm-authority" sample
|
|
595
|
+
printf 'needs-decision [key=stale]: old parent question\n' > "$home/state/authority.status"
|
|
596
|
+
fakebin=$(make_fakebin "$home")
|
|
597
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
598
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
599
|
+
printf '%s' "$canonical" | jq -e '
|
|
600
|
+
.secondmate_current.records[] | select(.id == "authority")
|
|
601
|
+
| .current.state == "no_active_work"
|
|
602
|
+
and .decisions_open == []
|
|
603
|
+
and .contradiction == true
|
|
604
|
+
and (.parent_event.open_decisions | any(.key == "stale" and .verb == "needs-decision"))
|
|
605
|
+
' >/dev/null || fail "parent decision crossed structured-home authority boundary: $canonical"
|
|
606
|
+
json=$(run "$home" "$fakebin" --json)
|
|
607
|
+
printf '%s' "$json" | jq -e '
|
|
608
|
+
(.secondmates | any(.[]; .id == "authority" and .state == "no_active_work" and .contradiction == true))
|
|
609
|
+
and (.decisions_open | any(.[]; .id == "authority") | not)
|
|
610
|
+
' >/dev/null || fail "bearings promoted a stale parent decision: $json"
|
|
611
|
+
pass "parent decisions remain untrusted contradiction evidence"
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
test_parent_evidence_reconciles_by_verb_and_key() {
|
|
615
|
+
local home hold blocked decision fakebin canonical mate child
|
|
616
|
+
home=$(make_home keyed-parent-evidence)
|
|
617
|
+
hold="$TMP_ROOT/keyed-parent-hold-home"
|
|
618
|
+
blocked="$TMP_ROOT/keyed-parent-blocked-home"
|
|
619
|
+
decision="$TMP_ROOT/keyed-parent-decision-home"
|
|
620
|
+
make_valid_secondmate_home hold "$hold"
|
|
621
|
+
make_valid_secondmate_home blocked "$blocked"
|
|
622
|
+
make_valid_secondmate_home decision "$decision"
|
|
623
|
+
append_secondmate_registry "$home" hold "$hold"
|
|
624
|
+
append_secondmate_registry "$home" blocked "$blocked"
|
|
625
|
+
append_secondmate_registry "$home" decision "$decision"
|
|
626
|
+
fm_write_secondmate_meta "$home/state/hold.meta" "$hold" "firstmate:fm-hold" sample
|
|
627
|
+
fm_write_secondmate_meta "$home/state/blocked.meta" "$blocked" "firstmate:fm-blocked" sample
|
|
628
|
+
fm_write_secondmate_meta "$home/state/decision.meta" "$decision" "firstmate:fm-decision" sample
|
|
629
|
+
printf 'working [key=stale-work]: old work still running\n' > "$home/state/hold.status"
|
|
630
|
+
printf 'paused [key=legal-release]: waiting for legal release\n' >> "$home/state/hold.status"
|
|
631
|
+
printf 'paused: legacy pause without an identity\n' >> "$home/state/hold.status"
|
|
632
|
+
printf 'blocked [key=vendor-release]: waiting for vendor release\n' > "$home/state/blocked.status"
|
|
633
|
+
printf 'blocked: legacy block without an identity\n' >> "$home/state/blocked.status"
|
|
634
|
+
printf 'needs-decision [key=stale-route]: choose the old route\n' > "$home/state/decision.status"
|
|
635
|
+
printf 'working: legacy work without an identity\n' >> "$home/state/decision.status"
|
|
636
|
+
cat > "$hold/data/backlog.md" <<'EOF'
|
|
637
|
+
## In flight
|
|
638
|
+
|
|
639
|
+
## Queued
|
|
640
|
+
- [ ] legal-release - Legal release blocked-by: external-legal - legal review (repo: sample) (kind: ship)
|
|
641
|
+
|
|
642
|
+
## Done
|
|
643
|
+
EOF
|
|
644
|
+
cat > "$blocked/data/backlog.md" <<'EOF'
|
|
645
|
+
## In flight
|
|
646
|
+
|
|
647
|
+
## Queued
|
|
648
|
+
- [ ] vendor-release - Vendor release blocked-by: external-vendor - vendor review (repo: sample) (kind: ship)
|
|
649
|
+
|
|
650
|
+
## Done
|
|
651
|
+
EOF
|
|
652
|
+
child='decision-child'
|
|
653
|
+
mkdir -p "$decision/projects/$child"
|
|
654
|
+
cat > "$decision/data/backlog.md" <<EOF
|
|
655
|
+
## In flight
|
|
656
|
+
- [ ] $child - Decision child (repo: sample) (kind: ship) (since 2026-07-11)
|
|
657
|
+
|
|
658
|
+
## Queued
|
|
659
|
+
|
|
660
|
+
## Done
|
|
661
|
+
EOF
|
|
662
|
+
fm_write_meta "$decision/state/$child.meta" \
|
|
663
|
+
"window=firstmate:fm-$child" "worktree=$decision/projects/$child" "project=sample" \
|
|
664
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
665
|
+
record_claude_state "$decision/state" "$child" idle
|
|
666
|
+
printf 'needs-decision [key=live-route]: choose the current route\n' > "$decision/state/$child.status"
|
|
667
|
+
fakebin=$(make_fakebin "$home")
|
|
668
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
669
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
670
|
+
printf '%s' "$canonical" | jq -e '
|
|
671
|
+
(.secondmate_current.records[] | select(.id == "hold")
|
|
672
|
+
| .current.state == "externally_held"
|
|
673
|
+
and .contradiction == true
|
|
674
|
+
and .terminal_evidence.captured == false
|
|
675
|
+
and (.parent_event.reconciliation.activities
|
|
676
|
+
| any(.verb == "paused" and .key == "legal-release" and .verdict == "corroborates"))
|
|
677
|
+
and (.parent_event.reconciliation.activities
|
|
678
|
+
| any(.verb == "paused" and .key == "default" and .verdict == "inconclusive" and .matched == null))
|
|
679
|
+
and (.parent_event.reconciliation.activities
|
|
680
|
+
| any(.verb == "working" and .key == "stale-work" and .verdict == "contradicts")))
|
|
681
|
+
and (.secondmate_current.records[] | select(.id == "blocked")
|
|
682
|
+
| .current.state == "externally_held"
|
|
683
|
+
and .contradiction == false
|
|
684
|
+
and (.parent_event.reconciliation.decisions
|
|
685
|
+
| any(.verb == "blocked" and .key == "vendor-release" and .verdict == "corroborates"))
|
|
686
|
+
and (.parent_event.reconciliation.decisions
|
|
687
|
+
| any(.verb == "blocked" and .key == "default" and .verdict == "inconclusive" and .matched == null)))
|
|
688
|
+
and (.secondmate_current.records[] | select(.id == "decision")
|
|
689
|
+
| .current.state == "captain_decision"
|
|
690
|
+
and .contradiction == true
|
|
691
|
+
and .terminal_evidence.captured == false
|
|
692
|
+
and (.parent_event.reconciliation.activities
|
|
693
|
+
| any(.verb == "working" and .key == "default" and .verdict == "inconclusive" and .matched == null))
|
|
694
|
+
and (.parent_event.reconciliation.decisions
|
|
695
|
+
| any(.verb == "needs-decision" and .key == "stale-route" and .verdict == "contradicts")))
|
|
696
|
+
' >/dev/null || fail "parent evidence was not reconciled by verb and key: $canonical"
|
|
697
|
+
pass "parent evidence reconciliation distinguishes matching holds, blocks, and decisions"
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
test_nonprogressing_child_states_are_explicit() {
|
|
701
|
+
local home mate fakebin canonical
|
|
702
|
+
home=$(make_home child-state-classification)
|
|
703
|
+
mate="$TMP_ROOT/child-state-classification-home"
|
|
704
|
+
make_valid_secondmate_home states "$mate"
|
|
705
|
+
append_secondmate_registry "$home" states "$mate"
|
|
706
|
+
mkdir -p "$mate/projects/parked" "$mate/projects/done" "$mate/projects/failed"
|
|
707
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
708
|
+
## In flight
|
|
709
|
+
- [ ] parked - Parked child (repo: sample) (kind: ship) (since 2026-07-11)
|
|
710
|
+
|
|
711
|
+
## Queued
|
|
712
|
+
|
|
713
|
+
## Done
|
|
714
|
+
EOF
|
|
715
|
+
fm_write_meta "$mate/state/parked.meta" \
|
|
716
|
+
"window=firstmate:fm-parked" "worktree=$mate/projects/parked" "project=sample" \
|
|
717
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
718
|
+
record_claude_state "$mate/state" parked idle
|
|
719
|
+
printf 'needs-decision [key=parked]: choose a route\n' > "$mate/state/parked.status"
|
|
720
|
+
fakebin=$(make_fakebin "$home")
|
|
721
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
722
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
723
|
+
printf '%s' "$canonical" | jq -e '
|
|
724
|
+
.secondmate_current.records[] | select(.id == "states")
|
|
725
|
+
| .current.state == "captain_decision"
|
|
726
|
+
and .active_children == []
|
|
727
|
+
and (.holds | any(.id == "parked" and .source == "child-state"))
|
|
728
|
+
' >/dev/null || fail "parked child was classified as active work: $canonical"
|
|
729
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
730
|
+
## In flight
|
|
731
|
+
|
|
732
|
+
## Queued
|
|
733
|
+
|
|
734
|
+
## Done
|
|
735
|
+
EOF
|
|
736
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
737
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
738
|
+
printf '%s' "$canonical" | jq -e '
|
|
739
|
+
.secondmate_current.records[] | select(.id == "states")
|
|
740
|
+
| .current.state == "captain_decision"
|
|
741
|
+
and (.current.reason | contains("live child state has no in-flight backlog item"))
|
|
742
|
+
and (.current.reason | contains("parked=parked"))
|
|
743
|
+
and .provenance.selected == "structured-home"
|
|
744
|
+
and .provenance.trust == "partial-structured"
|
|
745
|
+
and .invalidity == {kind:"unowned_current",ids:["parked"]}
|
|
746
|
+
and [.decisions_open[].key] == ["parked"]
|
|
747
|
+
' >/dev/null || fail "unowned held child lost its classification or decisions: $canonical"
|
|
748
|
+
cat > "$mate/data/backlog.md" <<'EOF'
|
|
749
|
+
## In flight
|
|
750
|
+
- [ ] done - Done child still in flight (repo: sample) (kind: ship) (since 2026-07-11)
|
|
751
|
+
- [ ] failed - Failed child still in flight (repo: sample) (kind: ship) (since 2026-07-11)
|
|
752
|
+
|
|
753
|
+
## Queued
|
|
754
|
+
|
|
755
|
+
## Done
|
|
756
|
+
EOF
|
|
757
|
+
fm_write_meta "$mate/state/done.meta" \
|
|
758
|
+
"window=firstmate:fm-done" "worktree=$mate/projects/done" "project=sample" \
|
|
759
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
760
|
+
fm_write_meta "$mate/state/failed.meta" \
|
|
761
|
+
"window=firstmate:fm-failed" "worktree=$mate/projects/failed" "project=sample" \
|
|
762
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
763
|
+
record_claude_state "$mate/state" "done" idle
|
|
764
|
+
record_claude_state "$mate/state" failed idle
|
|
765
|
+
printf 'done: complete\n' > "$mate/state/done.status"
|
|
766
|
+
printf 'failed: stopped\n' > "$mate/state/failed.status"
|
|
767
|
+
rm "$mate/state/parked.meta" "$mate/state/parked.status"
|
|
768
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
769
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
770
|
+
printf '%s' "$canonical" | jq -e '
|
|
771
|
+
.secondmate_current.records[] | select(.id == "states")
|
|
772
|
+
| .current.state == "no_active_work"
|
|
773
|
+
and (.current.reason | contains("terminal child state"))
|
|
774
|
+
and (.current.reason | contains("done=done"))
|
|
775
|
+
and (.current.reason | contains("failed=failed"))
|
|
776
|
+
and .provenance.selected == "structured-home"
|
|
777
|
+
and .provenance.trust == "partial-structured"
|
|
778
|
+
and .invalidity == {kind:"terminal_in_flight",ids:["done","failed"]}
|
|
779
|
+
' >/dev/null || fail "terminal in-flight rows discarded the readable home: $canonical"
|
|
780
|
+
pass "nonprogressing child states are explicit and inconsistent terminal rows invalidate"
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
test_registry_unavailability_and_bounds_are_explicit() {
|
|
784
|
+
local home fakebin json canonical id mate boundary
|
|
785
|
+
home=$(make_home registry-unavailable)
|
|
786
|
+
mate="$TMP_ROOT/registry-hidden"
|
|
787
|
+
make_valid_secondmate_home hidden "$mate"
|
|
788
|
+
printf -- '- hidden - fixture (home: %s; scope: fixture; projects: sample; added 2026-07-11)\n' "$mate" > "$home/data/secondmates.md"
|
|
789
|
+
fm_write_secondmate_meta "$home/state/hidden.meta" "$mate" "firstmate:fm-hidden" sample
|
|
790
|
+
chmod 000 "$home/data/secondmates.md"
|
|
791
|
+
fakebin=$(make_fakebin "$home")
|
|
792
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
793
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
794
|
+
json=$(run "$home" "$fakebin" --json)
|
|
795
|
+
chmod 600 "$home/data/secondmates.md"
|
|
796
|
+
printf '%s' "$canonical" | jq -e '
|
|
797
|
+
.secondmate_current.registry.complete == false
|
|
798
|
+
and (.secondmate_current.records[] | select(.id == "hidden")
|
|
799
|
+
| .registered == null
|
|
800
|
+
and (.current.reason | contains("registration is unknown")))
|
|
801
|
+
' >/dev/null || fail "unavailable registry produced false unregistered provenance: $canonical"
|
|
802
|
+
printf '%s' "$json" | jq -e '
|
|
803
|
+
(.secondmates | any(.[]; .id == "(registry)" and .state == "unknown"
|
|
804
|
+
and .provenance == "registered-table" and .freshness == "unavailable"))
|
|
805
|
+
and (.omitted | any(.surface | contains("secondmate registry unavailable")))
|
|
806
|
+
' >/dev/null || fail "unreadable registry disappeared from bearings: $json"
|
|
807
|
+
home=$(make_home registry-bounds)
|
|
808
|
+
: > "$home/data/secondmates.md"
|
|
809
|
+
for id in one two three; do
|
|
810
|
+
mate="$TMP_ROOT/registry-$id"
|
|
811
|
+
make_valid_secondmate_home "$id" "$mate"
|
|
812
|
+
append_secondmate_registry "$home" "$id" "$mate"
|
|
813
|
+
done
|
|
814
|
+
fakebin=$(make_fakebin "$home")
|
|
815
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
816
|
+
FM_SNAPSHOT_REGISTRY_RECORDS=2 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
817
|
+
printf '%s' "$canonical" | jq -e '
|
|
818
|
+
.secondmate_current.registry
|
|
819
|
+
| .available == true and .provenance == "registered-table"
|
|
820
|
+
and .freshness.status == "fresh" and .records_truncated == true
|
|
821
|
+
and .records_in_window == 3 and (.records | length) == 2
|
|
822
|
+
and (.reasons | index("record_limit") != null)
|
|
823
|
+
' >/dev/null || fail "registry record bound was not enforced or disclosed: $canonical"
|
|
824
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
825
|
+
FM_SNAPSHOT_REGISTRY_LINES=2 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
826
|
+
printf '%s' "$canonical" | jq -e '
|
|
827
|
+
.secondmate_current.registry
|
|
828
|
+
| .input_truncated == true and .records_truncated == false
|
|
829
|
+
and .lines_in_window == 2 and (.records | length) == 2
|
|
830
|
+
and .reasons == ["line_limit"]
|
|
831
|
+
' >/dev/null || fail "registry line bound was not enforced or disclosed: $canonical"
|
|
832
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
833
|
+
FM_SNAPSHOT_REGISTRY_BYTES=100 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
834
|
+
printf '%s' "$canonical" | jq -e '
|
|
835
|
+
.secondmate_current.registry
|
|
836
|
+
| .input_truncated == true and (.reasons | index("byte_limit") != null)
|
|
837
|
+
and .records_in_window < 3
|
|
838
|
+
' >/dev/null || fail "registry byte bound was not enforced or disclosed: $canonical"
|
|
839
|
+
boundary=$(LC_ALL=C head -n 1 "$home/data/secondmates.md" | wc -c | tr -d ' ')
|
|
840
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
841
|
+
FM_SNAPSHOT_REGISTRY_BYTES="$((boundary - 1))" "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
842
|
+
printf '%s' "$canonical" | jq -e '
|
|
843
|
+
.secondmate_current.registry
|
|
844
|
+
| .input_truncated == true and .complete == false
|
|
845
|
+
and (.reasons | index("byte_limit") != null)
|
|
846
|
+
' >/dev/null || fail "registry newline byte boundary hid truncation: $canonical"
|
|
847
|
+
json=$(FM_SNAPSHOT_REGISTRY_RECORDS=2 run "$home" "$fakebin" --json)
|
|
848
|
+
printf '%s' "$json" | jq -e '
|
|
849
|
+
.omitted | any(.surface == "secondmate registry records omitted by bounded read")
|
|
850
|
+
' >/dev/null || fail "bearings omitted registry truncation disclosure: $json"
|
|
851
|
+
mate="$TMP_ROOT/registry-z-hidden"
|
|
852
|
+
make_valid_secondmate_home z-hidden "$mate"
|
|
853
|
+
append_secondmate_registry "$home" z-hidden "$mate"
|
|
854
|
+
fm_write_secondmate_meta "$home/state/z-hidden.meta" "$mate" "firstmate:fm-z-hidden" sample
|
|
855
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
856
|
+
FM_SNAPSHOT_REGISTRY_RECORDS=3 "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
857
|
+
printf '%s' "$canonical" | jq -e '
|
|
858
|
+
.secondmate_current.registry.complete == false
|
|
859
|
+
and (.secondmate_current.records[] | select(.id == "z-hidden")
|
|
860
|
+
| .registered == null
|
|
861
|
+
and (.current.reason | contains("registration is unknown")))
|
|
862
|
+
' >/dev/null || fail "truncated registry produced false unregistered provenance: $canonical"
|
|
863
|
+
pass "registry unavailability and bounded truncation remain explicit"
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
test_current_landed_baseline_is_repeatable_and_prior_report_independent() {
|
|
867
|
+
local home fakebin one two
|
|
868
|
+
home=$(make_home standalone-baseline); write_fixture "$home"
|
|
869
|
+
cat > "$home/data/status-report-2026-07-10.md" <<'EOF'
|
|
870
|
+
# Misleading old report
|
|
871
|
+
|
|
872
|
+
## Recently Landed
|
|
873
|
+
- fake-old-item
|
|
874
|
+
|
|
875
|
+
## Underway
|
|
876
|
+
- Phase 7 started
|
|
877
|
+
EOF
|
|
878
|
+
fakebin=$(make_fakebin "$home")
|
|
879
|
+
one=$(run "$home" "$fakebin" --json)
|
|
880
|
+
two=$(run "$home" "$fakebin" --json)
|
|
881
|
+
[ "$(printf '%s' "$one" | jq -c '.landed')" = "$(printf '%s' "$two" | jq -c '.landed')" ] \
|
|
882
|
+
|| fail "the same structured state produced different recent-completion baselines"
|
|
883
|
+
printf '%s' "$two" | jq -e '
|
|
884
|
+
(.landed | any(.id == "done-a"))
|
|
885
|
+
and (.landed | any(.id == "mate-landed"))
|
|
886
|
+
and (.landed | any(.id == "fake-old-item") | not)
|
|
887
|
+
and (.in_flight | any(.doing == "Phase 7 started") | not)
|
|
888
|
+
' >/dev/null || fail "prior status report influenced the standalone snapshot: $two"
|
|
889
|
+
pass "repeated snapshots keep the same current landed baseline and ignore prior reports"
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
test_default_is_bounded_and_local_only() {
|
|
893
|
+
local home fakebin toon json
|
|
894
|
+
home=$(make_home bounded); write_fixture "$home"
|
|
895
|
+
fakebin=$(make_fakebin "$home"); : > "$home/net.log"
|
|
896
|
+
toon=$(run "$home" "$fakebin")
|
|
897
|
+
json=$(run "$home" "$fakebin" --json)
|
|
898
|
+
# Bound: well under the ~50 KB tool-display limit.
|
|
899
|
+
[ "${#toon}" -lt 50000 ] || fail "default TOON must stay under the display bound, got ${#toon}"
|
|
900
|
+
# TOON is materially smaller than the canonical snapshot it projects.
|
|
901
|
+
local canon; canon=$(PATH="$fakebin:$PATH" FM_HOME="$home" "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
902
|
+
[ "${#toon}" -lt "${#canon}" ] || fail "projection must be smaller than the canonical snapshot"
|
|
903
|
+
# Local-only: no GitHub/network call on the default path.
|
|
904
|
+
[ ! -s "$home/net.log" ] || fail "default run must make no gh/gh-axi call, got: $(cat "$home/net.log")"
|
|
905
|
+
# Definitive not-requested PR state, never a silent omission.
|
|
906
|
+
assert_contains "$toon" 'prs: "not_requested' "default must state PR checks were not requested"
|
|
907
|
+
assert_contains "$toon" "live PR discovery + checks,\"--include-prs\"" "omitted must mark the dropped live-PR surface"
|
|
908
|
+
# Valid JSON, correct schema.
|
|
909
|
+
printf '%s' "$json" | jq -e '.schema == "fm-bearings.v1"' >/dev/null || fail "json schema wrong"
|
|
910
|
+
pass "default output is bounded, local-only, and marks omitted surfaces"
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
test_toon_json_parity() {
|
|
914
|
+
local home fakebin toon json keys k
|
|
915
|
+
home=$(make_home parity); write_fixture "$home"
|
|
916
|
+
fakebin=$(make_fakebin "$home")
|
|
917
|
+
toon=$(run "$home" "$fakebin")
|
|
918
|
+
json=$(run "$home" "$fakebin" --json)
|
|
919
|
+
# Same top-level keys in both representations.
|
|
920
|
+
keys=$(printf '%s' "$json" | jq -r 'keys_unsorted[]')
|
|
921
|
+
for k in $keys; do
|
|
922
|
+
if printf '%s' "$json" | jq -e --arg k "$k" '.[$k] | type == "array"' >/dev/null; then
|
|
923
|
+
local n hdr
|
|
924
|
+
n=$(printf '%s' "$json" | jq --arg k "$k" '.[$k] | length')
|
|
925
|
+
if [ "$n" = 0 ]; then
|
|
926
|
+
assert_contains "$toon" "$k: []" "empty array $k must render as 'key: []'"
|
|
927
|
+
else
|
|
928
|
+
# Header must declare the same count and the same field set.
|
|
929
|
+
hdr=$(printf '%s' "$toon" | grep -E "^$k\[[0-9]+\]\{" || true)
|
|
930
|
+
[ -n "$hdr" ] || fail "TOON missing tabular header for $k"
|
|
931
|
+
assert_contains "$hdr" "[$n]" "TOON $k row count must equal JSON length $n"
|
|
932
|
+
local jfields tfields
|
|
933
|
+
jfields=$(printf '%s' "$json" | jq -r --arg k "$k" '.[$k][0] | keys_unsorted | join(",")')
|
|
934
|
+
tfields=$(printf '%s' "$hdr" | sed -E 's/^[^{]*\{//; s/\}:.*$//; s/"//g')
|
|
935
|
+
[ "$jfields" = "$tfields" ] || fail "TOON $k fields ($tfields) must equal JSON fields ($jfields)"
|
|
936
|
+
fi
|
|
937
|
+
else
|
|
938
|
+
# Scalar: the key must appear as a "key: value" line.
|
|
939
|
+
assert_contains "$toon" "$k: " "TOON must carry scalar field $k"
|
|
940
|
+
fi
|
|
941
|
+
done
|
|
942
|
+
pass "TOON and JSON are parity representations of the same model"
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
test_open_decision_surfaces_end_to_end() {
|
|
946
|
+
local home fakebin json
|
|
947
|
+
home=$(make_home e2e-decision); write_fixture "$home"
|
|
948
|
+
fakebin=$(make_fakebin "$home")
|
|
949
|
+
json=$(run "$home" "$fakebin" --json)
|
|
950
|
+
printf '%s' "$json" | jq -e '
|
|
951
|
+
.decisions_open | any(.[]; .id == "mate/mate-decision-race"
|
|
952
|
+
and .key == "mate-decision-race" and .verb == "captain-hold")
|
|
953
|
+
' >/dev/null || fail "an authoritative captain hold must surface in decisions_open: $json"
|
|
954
|
+
pass "an authoritative captain hold surfaces end-to-end"
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
test_report_pointers_surface() {
|
|
958
|
+
local home fakebin json
|
|
959
|
+
home=$(make_home reports); write_fixture "$home"
|
|
960
|
+
fakebin=$(make_fakebin "$home")
|
|
961
|
+
json=$(run "$home" "$fakebin" --json)
|
|
962
|
+
printf '%s' "$json" | jq -e --arg p "$home/data/scout-x/report.md" '
|
|
963
|
+
.reports | any(.[]; .id == "scout-x" and .path == $p)
|
|
964
|
+
' >/dev/null || fail "current scout report pointer must surface: $json"
|
|
965
|
+
pass "current report pointers surface"
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
test_superseded_queued_item_dropped_by_default() {
|
|
969
|
+
local home fakebin json
|
|
970
|
+
home=$(make_home superseded); write_fixture "$home"
|
|
971
|
+
fakebin=$(make_fakebin "$home")
|
|
972
|
+
json=$(run "$home" "$fakebin" --json)
|
|
973
|
+
printf '%s' "$json" | jq -e '
|
|
974
|
+
(.gates | any(.[]; .id == "live-gate")) and (.gates | any(.[]; .id == "dead-gate") | not)
|
|
975
|
+
' >/dev/null || fail "default gates must include live and drop superseded: $json"
|
|
976
|
+
json=$(run "$home" "$fakebin" --json --all-queued)
|
|
977
|
+
printf '%s' "$json" | jq -e '.gates | any(.[]; .id == "dead-gate")' >/dev/null \
|
|
978
|
+
|| fail "--all-queued must restore the superseded item"
|
|
979
|
+
pass "superseded queued items are dropped by default and restored with --all-queued"
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
# The collapsed captain-call contract: any due, unblocked captain-held task is
|
|
983
|
+
# Captain's Call whatever its kind; a date-deferred hold is a dated gate until
|
|
984
|
+
# due; a prose-deferred hold leaves the default views with a disclosure; and
|
|
985
|
+
# Recently Landed excludes only what closed while still held for the captain.
|
|
986
|
+
test_collapsed_captain_call_deferral_and_landed() {
|
|
987
|
+
local home fakebin json
|
|
988
|
+
home=$(make_home collapsed-call)
|
|
989
|
+
mkdir -p "$home/data"
|
|
990
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
991
|
+
## In flight
|
|
992
|
+
|
|
993
|
+
## Queued
|
|
994
|
+
- [ ] work-gate - Captain-gated ship work (repo: firstmate) (kind: ship) (hold: captain go needed) (hold-kind: captain)
|
|
995
|
+
- [ ] later-call - Deferred captain call (repo: firstmate) (kind: captain) (hold: revisit with the captain) (hold-kind: captain) (hold-until: 2026-08-01)
|
|
996
|
+
- [ ] due-call - Due captain call (repo: firstmate) (kind: captain) (hold: overdue captain choice) (hold-kind: captain) (hold-until: 2026-07-11)
|
|
997
|
+
- [ ] parked-call - Prose-parked captain call (repo: firstmate) (kind: ship) (hold: DEFERRED by captain revisit later) (hold-kind: captain)
|
|
998
|
+
- [ ] external-gate - Externally held work (repo: firstmate) (kind: ship) (hold: upstream release pending) (hold-kind: external)
|
|
999
|
+
|
|
1000
|
+
## Done
|
|
1001
|
+
- [x] answered-call - Answered captain question (repo: firstmate) (kind: captain) (done 2026-07-10) (hold: captain choice pending) (hold-kind: captain)
|
|
1002
|
+
- [x] shipped-work - Ordinary landed work (repo: firstmate) (kind: ship) (merged 2026-07-10)
|
|
1003
|
+
EOF
|
|
1004
|
+
fakebin=$(make_fakebin "$home")
|
|
1005
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1006
|
+
printf '%s' "$json" | jq -e '
|
|
1007
|
+
(.decisions_open | any(.[]; .id == "work-gate"))
|
|
1008
|
+
and (.decisions_open | any(.[]; .id == "due-call"))
|
|
1009
|
+
and (.decisions_open | any(.[]; .id == "later-call") | not)
|
|
1010
|
+
and (.decisions_open | any(.[]; .id == "parked-call") | not)
|
|
1011
|
+
and (.decisions_open | any(.[]; .id == "external-gate") | not)
|
|
1012
|
+
and (.gates | any(.[]; .id == "later-call" and (.reason | startswith("until 2026-08-01"))))
|
|
1013
|
+
and (.gates | any(.[]; .id == "work-gate") | not)
|
|
1014
|
+
and (.gates | any(.[]; .id == "parked-call") | not)
|
|
1015
|
+
and (.gates | any(.[]; .id == "external-gate"))
|
|
1016
|
+
and (.landed | any(.[]; .id == "shipped-work"))
|
|
1017
|
+
and (.landed | any(.[]; .id == "answered-call") | not)
|
|
1018
|
+
and (.omitted | any(.[]; .surface | startswith("captain holds marked deferred")))
|
|
1019
|
+
' >/dev/null || fail "the collapsed captain-call projection is wrong: $json"
|
|
1020
|
+
json=$(run "$home" "$fakebin" --json --all-decisions --all-queued)
|
|
1021
|
+
printf '%s' "$json" | jq -e '
|
|
1022
|
+
(.decisions_open | any(.[]; .id == "parked-call"))
|
|
1023
|
+
and (.gates | any(.[]; .id == "parked-call") | not)
|
|
1024
|
+
' >/dev/null || fail "--all-decisions must reveal the prose-deferred call: $json"
|
|
1025
|
+
pass "captain-held tasks of any kind reach Captain's Call, deferral is honored, and landed excludes answered calls"
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
test_include_prs_is_the_only_fetch_path() {
|
|
1029
|
+
local home fakebin json
|
|
1030
|
+
home=$(make_home prs); write_fixture "$home"
|
|
1031
|
+
fakebin=$(make_fakebin "$home"); : > "$home/net.log"
|
|
1032
|
+
json=$(run "$home" "$fakebin" --include-prs --json)
|
|
1033
|
+
# Now gh WAS called, exactly for pr list.
|
|
1034
|
+
grep -q '^gh pr list ' "$home/net.log" || fail "--include-prs must call gh pr list"
|
|
1035
|
+
printf '%s' "$json" | jq -e '
|
|
1036
|
+
.prs | startswith("checked")
|
|
1037
|
+
' >/dev/null || fail "--include-prs must report checked PR state"
|
|
1038
|
+
printf '%s' "$json" | jq -e '
|
|
1039
|
+
.candidate_prs | any(.[]; .num == "9" and .task == "ship-task" and .checks == "passing" and .review == "APPROVED")
|
|
1040
|
+
' >/dev/null || fail "candidate_prs must carry the fetched PR cross-referenced to its task: $json"
|
|
1041
|
+
pass "--include-prs is the only path that fetches, and it enriches correctly"
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
test_partial_github_failure_degrades() {
|
|
1045
|
+
local home fakebin json rc
|
|
1046
|
+
home=$(make_home partial); write_fixture "$home"
|
|
1047
|
+
fakebin=$(make_fakebin "$home")
|
|
1048
|
+
json=$(FAKE_GH_FAIL=1 run "$home" "$fakebin" --include-prs --json); rc=$?
|
|
1049
|
+
expect_code 0 "$rc" "a PR-fetch failure must not crash the view"
|
|
1050
|
+
printf '%s' "$json" | jq -e '
|
|
1051
|
+
.schema == "fm-bearings.v1"
|
|
1052
|
+
and (.candidate_prs | length) == 0
|
|
1053
|
+
and (.prs | test("unavailable"))
|
|
1054
|
+
and (.in_flight | length) > 0
|
|
1055
|
+
' >/dev/null || fail "on gh failure the view must still emit, with an unavailable note: $json"
|
|
1056
|
+
pass "a partial GitHub failure degrades gracefully"
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
test_perl_fallback_bounds_github_call() {
|
|
1060
|
+
local home fakebin toolbin cmd json started elapsed
|
|
1061
|
+
home=$(make_home perl-timeout); write_fixture "$home"
|
|
1062
|
+
fakebin=$(make_fakebin "$home")
|
|
1063
|
+
toolbin="$home/toolbin"
|
|
1064
|
+
mkdir -p "$toolbin"
|
|
1065
|
+
for cmd in bash dirname basename jq date sed git grep tail cut tr head sort wc perl sleep cat find; do
|
|
1066
|
+
ln -s "$(command -v "$cmd")" "$toolbin/$cmd"
|
|
1067
|
+
done
|
|
1068
|
+
started=$(date +%s)
|
|
1069
|
+
json=$(PATH="$fakebin:$toolbin" FM_HOME="$home" FM_BEARINGS_NOW=2026-07-11T18:00:00Z \
|
|
1070
|
+
FM_BEARINGS_PR_TIMEOUT=1 NET_LOG="$home/net.log" FAKE_GH_SLEEP=1 "$BEARINGS" --include-prs --json)
|
|
1071
|
+
elapsed=$(( $(date +%s) - started ))
|
|
1072
|
+
[ "$elapsed" -lt 10 ] || fail "Perl fallback did not bound a stalled gh call (${elapsed}s)"
|
|
1073
|
+
printf '%s' "$json" | jq -e '.prs | test("unavailable")' >/dev/null \
|
|
1074
|
+
|| fail "timed-out gh call did not fail soft: $json"
|
|
1075
|
+
pass "Perl fallback bounds stalled GitHub calls without coreutils timeout"
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
write_large_fixture() { # <home> <count>
|
|
1079
|
+
local home=$1 count=$2 i id
|
|
1080
|
+
: > "$home/data/backlog.md"
|
|
1081
|
+
printf '## Queued\n' >> "$home/data/backlog.md"
|
|
1082
|
+
i=1
|
|
1083
|
+
while [ "$i" -le "$count" ]; do
|
|
1084
|
+
id="dead-$i"
|
|
1085
|
+
mkdir -p "$home/projects/$id" "$home/data/$id"
|
|
1086
|
+
printf '# Report\n' > "$home/data/$id/report.md"
|
|
1087
|
+
printf -- '- [ ] gate-%s - Gate %s blocked-by: task-%s (repo: repo-%s) (kind: ship)\n' "$i" "$i" "$i" "$i" >> "$home/data/backlog.md"
|
|
1088
|
+
printf -- '- [ ] decision-%s - Decision %s (repo: repo-%s) (kind: captain) (hold: captain choice pending) (hold-kind: captain)\n' "$i" "$i" "$i" >> "$home/data/backlog.md"
|
|
1089
|
+
fm_write_meta "$home/state/$id.meta" \
|
|
1090
|
+
"window=firstmate:fm-$id" \
|
|
1091
|
+
"worktree=$home/projects/$id" \
|
|
1092
|
+
"project=repo-$i" \
|
|
1093
|
+
"harness=codex" \
|
|
1094
|
+
"kind=scout" \
|
|
1095
|
+
"mode=scout" \
|
|
1096
|
+
"pr=https://github.com/acme/repo-$i/pull/$i"
|
|
1097
|
+
printf 'needs-decision [key=q%s]: choose %s\n' "$i" "$i" > "$home/state/$id.status"
|
|
1098
|
+
i=$((i + 1))
|
|
1099
|
+
done
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
test_section_caps_and_expansion_flags() {
|
|
1103
|
+
local home fakebin json expanded
|
|
1104
|
+
home=$(make_home caps); write_large_fixture "$home" 5
|
|
1105
|
+
fakebin=$(make_fakebin "$home")
|
|
1106
|
+
json=$(FM_BEARINGS_IN_FLIGHT=2 FM_BEARINGS_DECISIONS=2 FM_BEARINGS_GATES=2 \
|
|
1107
|
+
FM_BEARINGS_REPORTS=2 FM_BEARINGS_RECORDED_PRS=2 FM_BEARINGS_UNHEALTHY=2 \
|
|
1108
|
+
run "$home" "$fakebin" --json)
|
|
1109
|
+
printf '%s' "$json" | jq -e '
|
|
1110
|
+
(.in_flight|length) == 2 and (.decisions_open|length) == 2 and (.gates|length) == 2
|
|
1111
|
+
and (.reports|length) == 2 and (.recorded_prs|length) == 2 and (.unhealthy_endpoints|length) == 2
|
|
1112
|
+
and ([.omitted[].surface] | index("in_flight showing 2 of 5") != null)
|
|
1113
|
+
and ([.omitted[].surface] | index("decisions_open showing 2 of 5") != null)
|
|
1114
|
+
and ([.omitted[].surface] | index("gates showing 2 of 5") != null)
|
|
1115
|
+
and ([.omitted[].surface] | index("reports showing 2 of 5") != null)
|
|
1116
|
+
and ([.omitted[].surface] | index("recorded_prs showing 2 of 5") != null)
|
|
1117
|
+
and ([.omitted[].surface] | index("unhealthy_endpoints showing 2 of 5") != null)
|
|
1118
|
+
' >/dev/null || fail "section caps or counted omissions are wrong: $json"
|
|
1119
|
+
expanded=$(FM_BEARINGS_IN_FLIGHT=2 FM_BEARINGS_DECISIONS=2 FM_BEARINGS_GATES=2 \
|
|
1120
|
+
FM_BEARINGS_REPORTS=2 FM_BEARINGS_RECORDED_PRS=2 FM_BEARINGS_UNHEALTHY=2 \
|
|
1121
|
+
run "$home" "$fakebin" --json --all-in-flight --all-decisions --all-queued \
|
|
1122
|
+
--all-reports --all-recorded-prs --all-unhealthy)
|
|
1123
|
+
printf '%s' "$expanded" | jq -e '
|
|
1124
|
+
(.in_flight|length) == 5 and (.decisions_open|length) == 5 and (.gates|length) == 5
|
|
1125
|
+
and (.reports|length) == 5 and (.recorded_prs|length) == 5 and (.unhealthy_endpoints|length) == 5
|
|
1126
|
+
' >/dev/null || fail "section expansion flags did not reveal full sets: $expanded"
|
|
1127
|
+
pass "all fleet-sized sections are capped with counted opt-in expansion"
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
test_pr_repository_cap_and_expansion() {
|
|
1131
|
+
local home fakebin json expanded
|
|
1132
|
+
home=$(make_home repo-caps); write_large_fixture "$home" 5
|
|
1133
|
+
fakebin=$(make_fakebin "$home"); : > "$home/net.log"
|
|
1134
|
+
json=$(FM_BEARINGS_PR_REPOS=2 run "$home" "$fakebin" --include-prs --json)
|
|
1135
|
+
[ "$(grep -c '^gh pr list ' "$home/net.log")" = 2 ] || fail "default PR repository cap was not enforced"
|
|
1136
|
+
printf '%s' "$json" | jq -e '
|
|
1137
|
+
[.omitted[] | select(.surface == "PR repositories showing 2 of 5" and .reveal == "--all-pr-repos")] | length == 1
|
|
1138
|
+
' >/dev/null || fail "PR repository truncation was not recorded: $json"
|
|
1139
|
+
: > "$home/net.log"
|
|
1140
|
+
expanded=$(FM_BEARINGS_PR_REPOS=2 run "$home" "$fakebin" --include-prs --all-pr-repos --json)
|
|
1141
|
+
[ "$(grep -c '^gh pr list ' "$home/net.log")" = 5 ] || fail "--all-pr-repos did not reveal every repository"
|
|
1142
|
+
printf '%s' "$expanded" | jq -e '.candidate_prs | length == 5' >/dev/null \
|
|
1143
|
+
|| fail "expanded PR repository set did not enrich every repository: $expanded"
|
|
1144
|
+
pass "live PR enrichment caps repositories with counted expansion"
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
test_per_repository_pr_cap_is_disclosed() {
|
|
1148
|
+
local home fakebin json toon
|
|
1149
|
+
home=$(make_home pr-row-cap); write_fixture "$home"
|
|
1150
|
+
fakebin=$(make_fakebin "$home")
|
|
1151
|
+
json=$(FM_BEARINGS_PR_LIMIT=2 FAKE_GH_MANY=1 run "$home" "$fakebin" --include-prs --json)
|
|
1152
|
+
toon=$(FM_BEARINGS_PR_LIMIT=2 FAKE_GH_MANY=1 run "$home" "$fakebin" --include-prs)
|
|
1153
|
+
printf '%s' "$json" | jq -e '
|
|
1154
|
+
(.candidate_prs | length) == 2
|
|
1155
|
+
and (.prs | test("2 shown, at least 3 open; capped in 1 repo"))
|
|
1156
|
+
and ([.omitted[] | select(.surface == "candidate_prs showing 2 of at least 3; capped in 1 repo(s)" and .reveal == "raise FM_BEARINGS_PR_LIMIT")] | length) == 1
|
|
1157
|
+
' >/dev/null || fail "per-repository PR truncation was not disclosed: $json"
|
|
1158
|
+
assert_contains "$toon" 'candidate_prs showing 2 of at least 3' "TOON did not preserve PR truncation disclosure"
|
|
1159
|
+
pass "per-repository open-PR caps are disclosed with an expansion knob"
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
install_failing_jq() { # <fakebin> <model|toon>
|
|
1163
|
+
local fakebin=$1 phase=$2 real
|
|
1164
|
+
real=$(command -v jq)
|
|
1165
|
+
cat > "$fakebin/jq" <<SH
|
|
1166
|
+
#!/usr/bin/env bash
|
|
1167
|
+
case "\$*" in
|
|
1168
|
+
*'def trunc'*) [ "$phase" = model ] && exit 9 ;;
|
|
1169
|
+
*'def q:'*) [ "$phase" = toon ] && exit 9 ;;
|
|
1170
|
+
esac
|
|
1171
|
+
exec "$real" "\$@"
|
|
1172
|
+
SH
|
|
1173
|
+
chmod +x "$fakebin/jq"
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
test_projection_and_toon_fail_closed() {
|
|
1177
|
+
local home fakebin out err rc
|
|
1178
|
+
home=$(make_home fail-closed); write_fixture "$home"
|
|
1179
|
+
fakebin=$(make_fakebin "$home")
|
|
1180
|
+
install_failing_jq "$fakebin" model
|
|
1181
|
+
err="$home/model.err"
|
|
1182
|
+
out=$(run "$home" "$fakebin" --json 2> "$err"); rc=$?
|
|
1183
|
+
[ "$rc" -ne 0 ] || fail "projection failure exited successfully"
|
|
1184
|
+
[ -z "$out" ] || fail "projection failure emitted output"
|
|
1185
|
+
grep -F 'projection failed' "$err" >/dev/null || fail "projection failure lacked a diagnostic"
|
|
1186
|
+
install_failing_jq "$fakebin" toon
|
|
1187
|
+
err="$home/toon.err"
|
|
1188
|
+
out=$(run "$home" "$fakebin" 2> "$err"); rc=$?
|
|
1189
|
+
[ "$rc" -ne 0 ] || fail "TOON rendering failure exited successfully"
|
|
1190
|
+
[ -z "$out" ] || fail "TOON rendering failure emitted output"
|
|
1191
|
+
grep -F 'TOON rendering failed' "$err" >/dev/null || fail "TOON failure lacked a diagnostic"
|
|
1192
|
+
pass "projection and TOON rendering failures exit nonzero with diagnostics"
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
# The Lavish-103 defect, end to end: a COMPLETED scout that raised a decision and
|
|
1196
|
+
# then finished (done), whose report body reads like that decision, must surface as
|
|
1197
|
+
# a report POINTER only - never in decisions_open. Report prose must never open or
|
|
1198
|
+
# reopen a pending decision; only the keyed durable state does.
|
|
1199
|
+
test_completed_scout_report_not_pending() {
|
|
1200
|
+
local home fakebin json
|
|
1201
|
+
home=$(make_home completed-scout); write_fixture "$home"
|
|
1202
|
+
fakebin=$(make_fakebin "$home")
|
|
1203
|
+
mkdir -p "$home/projects/lav-wt" "$home/data/lavish-103"
|
|
1204
|
+
fm_write_meta "$home/state/lavish-103.meta" \
|
|
1205
|
+
"window=firstmate:fm-lavish-103" \
|
|
1206
|
+
"worktree=$home/projects/lav-wt" \
|
|
1207
|
+
"project=firstmate" \
|
|
1208
|
+
"harness=codex" \
|
|
1209
|
+
"kind=scout" \
|
|
1210
|
+
"mode=scout"
|
|
1211
|
+
printf 'needs-decision: adopt approach A or B for Lavish issue 103\n' > "$home/state/lavish-103.status"
|
|
1212
|
+
printf 'done: report ready at data/lavish-103/report.md\n' >> "$home/state/lavish-103.status"
|
|
1213
|
+
printf '# Lavish 103\nThe open question is whether to adopt approach A or B; this needs a captain decision.\n' > "$home/data/lavish-103/report.md"
|
|
1214
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1215
|
+
printf '%s' "$json" | jq -e '
|
|
1216
|
+
(.decisions_open | any(.[]; .id == "lavish-103") | not)
|
|
1217
|
+
and (.reports | any(.[]; .id == "lavish-103"))
|
|
1218
|
+
' >/dev/null || fail "completed scout must be a report pointer, never a pending decision: $json"
|
|
1219
|
+
pass "a completed scout with decision-like report prose is a pointer, not pending"
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
# Recently Landed must include merges a secondmate managed. Those completion records
|
|
1223
|
+
# live in the secondmate home's OWN backlog, not the main one, so the projection must
|
|
1224
|
+
# roll them up. Local, deterministic, no GitHub call.
|
|
1225
|
+
test_landed_includes_secondmate_home_merges() {
|
|
1226
|
+
local home fakebin json
|
|
1227
|
+
home=$(make_home mate-landed); write_fixture "$home"
|
|
1228
|
+
fakebin=$(make_fakebin "$home"); : > "$home/net.log"
|
|
1229
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1230
|
+
printf '%s' "$json" | jq -e '
|
|
1231
|
+
(.landed | any(.[]; .id == "mate-landed" and (.artifact | test("/pull/50"))))
|
|
1232
|
+
and (.landed | any(.[]; .id == "done-a"))
|
|
1233
|
+
' >/dev/null || fail "landed must merge secondmate-home Done with main-home Done: $json"
|
|
1234
|
+
# Still zero network on this default path.
|
|
1235
|
+
[ ! -s "$home/net.log" ] || fail "landed roll-up must make no gh/gh-axi call, got: $(cat "$home/net.log")"
|
|
1236
|
+
pass "landed includes secondmate-managed merges alongside main-home merges"
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
test_landed_default_balances_dominant_and_sparse_homes() {
|
|
1240
|
+
local home dominant sparse_a sparse_b sparse_c fakebin json i actual expected
|
|
1241
|
+
home=$(make_home landed-balanced-default)
|
|
1242
|
+
: > "$home/data/secondmates.md"
|
|
1243
|
+
printf '## Done\n' > "$home/data/backlog.md"
|
|
1244
|
+
dominant=$(make_landed_secondmate "$home" dominant)
|
|
1245
|
+
sparse_a=$(make_landed_secondmate "$home" sparse-a)
|
|
1246
|
+
sparse_b=$(make_landed_secondmate "$home" sparse-b)
|
|
1247
|
+
sparse_c=$(make_landed_secondmate "$home" sparse-c)
|
|
1248
|
+
i=1
|
|
1249
|
+
while [ "$i" -le 12 ]; do
|
|
1250
|
+
append_landed_row "$dominant" "$(printf 'dominant-landed-%02d' "$i")" \
|
|
1251
|
+
"$(printf 'Dominant landed %02d' "$i")" "$(printf '2026-07-%02d' "$((31 - i))")"
|
|
1252
|
+
i=$((i + 1))
|
|
1253
|
+
done
|
|
1254
|
+
i=1
|
|
1255
|
+
while [ "$i" -le 2 ]; do
|
|
1256
|
+
append_landed_row "$sparse_a" "$(printf 'sparse-a-landed-%02d' "$i")" \
|
|
1257
|
+
"$(printf 'Sparse A landed %02d' "$i")" "$(printf '2026-07-%02d' "$((12 - i))")"
|
|
1258
|
+
append_landed_row "$sparse_b" "$(printf 'sparse-b-landed-%02d' "$i")" \
|
|
1259
|
+
"$(printf 'Sparse B landed %02d' "$i")" "$(printf '2026-07-%02d' "$((10 - i))")"
|
|
1260
|
+
append_landed_row "$sparse_c" "$(printf 'sparse-c-landed-%02d' "$i")" \
|
|
1261
|
+
"$(printf 'Sparse C landed %02d' "$i")" "$(printf '2026-07-%02d' "$((8 - i))")"
|
|
1262
|
+
i=$((i + 1))
|
|
1263
|
+
done
|
|
1264
|
+
fakebin=$(make_fakebin "$home")
|
|
1265
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1266
|
+
actual=$(printf '%s' "$json" | jq -r '.landed[] | "\(.owner)/\(.id)"')
|
|
1267
|
+
expected='dominant/dominant-landed-01
|
|
1268
|
+
sparse-a/sparse-a-landed-01
|
|
1269
|
+
sparse-b/sparse-b-landed-01
|
|
1270
|
+
sparse-c/sparse-c-landed-01
|
|
1271
|
+
dominant/dominant-landed-02
|
|
1272
|
+
sparse-a/sparse-a-landed-02'
|
|
1273
|
+
[ "$actual" = "$expected" ] || fail "default landed selection was not balanced across homes: $actual"
|
|
1274
|
+
printf '%s' "$json" | jq -e '
|
|
1275
|
+
(.landed | length) == 6
|
|
1276
|
+
and ([.landed[].owner] | unique | length) == 4
|
|
1277
|
+
and ([.omitted[].surface] | any(test("landed showing 6 of 12")))
|
|
1278
|
+
' >/dev/null || fail "balanced landed default did not preserve cap disclosure: $json"
|
|
1279
|
+
pass "default landed selection balances one dominant home with sparse homes"
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
test_landed_default_refills_capacity_after_sparse_homes_exhaust() {
|
|
1283
|
+
local home dominant sparse fakebin json actual expected i
|
|
1284
|
+
home=$(make_home landed-sparse-refill)
|
|
1285
|
+
: > "$home/data/secondmates.md"
|
|
1286
|
+
printf '## Done\n' > "$home/data/backlog.md"
|
|
1287
|
+
dominant=$(make_landed_secondmate "$home" dominant)
|
|
1288
|
+
sparse=$(make_landed_secondmate "$home" sparse)
|
|
1289
|
+
i=1
|
|
1290
|
+
while [ "$i" -le 5 ]; do
|
|
1291
|
+
append_landed_row "$dominant" "$(printf 'dominant-landed-%02d' "$i")" \
|
|
1292
|
+
"$(printf 'Dominant landed %02d' "$i")" "$(printf '2026-07-%02d' "$((20 - i))")"
|
|
1293
|
+
i=$((i + 1))
|
|
1294
|
+
done
|
|
1295
|
+
append_landed_row "$sparse" sparse-landed-01 "Sparse landed 01" 2026-07-01
|
|
1296
|
+
fakebin=$(make_fakebin "$home")
|
|
1297
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1298
|
+
actual=$(printf '%s' "$json" | jq -r '.landed[] | "\(.owner)/\(.id)"')
|
|
1299
|
+
expected='dominant/dominant-landed-01
|
|
1300
|
+
sparse/sparse-landed-01
|
|
1301
|
+
dominant/dominant-landed-02
|
|
1302
|
+
dominant/dominant-landed-03
|
|
1303
|
+
dominant/dominant-landed-04
|
|
1304
|
+
dominant/dominant-landed-05'
|
|
1305
|
+
[ "$actual" = "$expected" ] || fail "sparse homes wasted landed capacity: $actual"
|
|
1306
|
+
pass "landed selection refills capacity after sparse homes exhaust"
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
test_landed_default_uses_deterministic_home_order_when_homes_exceed_cap() {
|
|
1310
|
+
local home mate fakebin json actual expected i id
|
|
1311
|
+
home=$(make_home landed-home-order)
|
|
1312
|
+
: > "$home/data/secondmates.md"
|
|
1313
|
+
printf '## Done\n' > "$home/data/backlog.md"
|
|
1314
|
+
i=1
|
|
1315
|
+
while [ "$i" -le 8 ]; do
|
|
1316
|
+
id=$(printf 'home-%02d' "$i")
|
|
1317
|
+
mate=$(make_landed_secondmate "$home" "$id")
|
|
1318
|
+
append_landed_row "$mate" "$id-landed-01" "$id landed 01" 2026-07-10
|
|
1319
|
+
i=$((i + 1))
|
|
1320
|
+
done
|
|
1321
|
+
fakebin=$(make_fakebin "$home")
|
|
1322
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1323
|
+
actual=$(printf '%s' "$json" | jq -r '.landed[] | "\(.owner)/\(.id)"')
|
|
1324
|
+
expected='home-01/home-01-landed-01
|
|
1325
|
+
home-02/home-02-landed-01
|
|
1326
|
+
home-03/home-03-landed-01
|
|
1327
|
+
home-04/home-04-landed-01
|
|
1328
|
+
home-05/home-05-landed-01
|
|
1329
|
+
home-06/home-06-landed-01'
|
|
1330
|
+
[ "$actual" = "$expected" ] || fail "landed home-order tie was not deterministic: $actual"
|
|
1331
|
+
printf '%s' "$json" | jq -e '
|
|
1332
|
+
([.omitted[].surface] | any(test("landed showing 6 of 8")))
|
|
1333
|
+
' >/dev/null || fail "more-homes-than-cap omission was not disclosed: $json"
|
|
1334
|
+
pass "landed selection uses deterministic home order when homes exceed the cap"
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
test_landed_default_preserves_internal_order_for_ties() {
|
|
1338
|
+
local home tie_a tie_b fakebin json actual expected
|
|
1339
|
+
home=$(make_home landed-ties)
|
|
1340
|
+
: > "$home/data/secondmates.md"
|
|
1341
|
+
printf '## Done\n' > "$home/data/backlog.md"
|
|
1342
|
+
tie_a=$(make_landed_secondmate "$home" tie-a)
|
|
1343
|
+
tie_b=$(make_landed_secondmate "$home" tie-b)
|
|
1344
|
+
append_landed_row "$tie_b" tie-b-a "Tie B A" 2026-07-10
|
|
1345
|
+
append_landed_row "$tie_b" tie-b-z "Tie B Z" 2026-07-10
|
|
1346
|
+
append_landed_row "$tie_a" tie-a-a "Tie A A" 2026-07-10
|
|
1347
|
+
append_landed_row "$tie_a" tie-a-z "Tie A Z" 2026-07-10
|
|
1348
|
+
fakebin=$(make_fakebin "$home")
|
|
1349
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1350
|
+
actual=$(printf '%s' "$json" | jq -r '.landed[] | "\(.owner)/\(.id)"')
|
|
1351
|
+
expected='tie-a/tie-a-z
|
|
1352
|
+
tie-b/tie-b-z
|
|
1353
|
+
tie-a/tie-a-a
|
|
1354
|
+
tie-b/tie-b-a'
|
|
1355
|
+
[ "$actual" = "$expected" ] || fail "landed tie ordering changed: $actual"
|
|
1356
|
+
pass "landed selection preserves deterministic home and internal tie ordering"
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
test_landed_default_handles_no_landed_items() {
|
|
1360
|
+
local home fakebin json
|
|
1361
|
+
home=$(make_home landed-empty)
|
|
1362
|
+
: > "$home/data/secondmates.md"
|
|
1363
|
+
printf '## Done\n' > "$home/data/backlog.md"
|
|
1364
|
+
fakebin=$(make_fakebin "$home")
|
|
1365
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1366
|
+
printf '%s' "$json" | jq -e '
|
|
1367
|
+
(.landed | length) == 0
|
|
1368
|
+
and ([.omitted[].surface] | any(test("landed")) | not)
|
|
1369
|
+
' >/dev/null || fail "empty landed set was not handled cleanly: $json"
|
|
1370
|
+
pass "landed selection handles no landed items"
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
test_all_landed_keeps_complete_global_order() {
|
|
1374
|
+
local home alpha beta fakebin json actual expected
|
|
1375
|
+
home=$(make_home landed-all-order)
|
|
1376
|
+
: > "$home/data/secondmates.md"
|
|
1377
|
+
printf '## Done\n' > "$home/data/backlog.md"
|
|
1378
|
+
alpha=$(make_landed_secondmate "$home" alpha)
|
|
1379
|
+
beta=$(make_landed_secondmate "$home" beta)
|
|
1380
|
+
append_landed_row "$alpha" alpha-old "Alpha old" 2026-07-01
|
|
1381
|
+
append_landed_row "$alpha" alpha-new "Alpha new" 2026-07-09
|
|
1382
|
+
append_landed_row "$beta" beta-new "Beta new" 2026-07-10
|
|
1383
|
+
append_landed_row "$beta" beta-mid "Beta mid" 2026-07-05
|
|
1384
|
+
fakebin=$(make_fakebin "$home")
|
|
1385
|
+
json=$(FM_BEARINGS_LANDED=1 run "$home" "$fakebin" --json --all-landed)
|
|
1386
|
+
actual=$(printf '%s' "$json" | jq -r '.landed[] | "\(.owner)/\(.id)"')
|
|
1387
|
+
expected='beta/beta-new
|
|
1388
|
+
alpha/alpha-new
|
|
1389
|
+
beta/beta-mid
|
|
1390
|
+
alpha/alpha-old'
|
|
1391
|
+
[ "$actual" = "$expected" ] || fail "--all-landed global order changed: $actual"
|
|
1392
|
+
printf '%s' "$json" | jq -e '
|
|
1393
|
+
(.landed | length) == 4
|
|
1394
|
+
and ([.omitted[].surface] | any(test("landed|snapshot layer")) | not)
|
|
1395
|
+
' >/dev/null || fail "--all-landed no longer revealed the complete landed set: $json"
|
|
1396
|
+
pass "--all-landed keeps the complete global landed output"
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
# The roll-up stays bounded: a per-home cap and an overall cap, both disclosed in
|
|
1400
|
+
# omitted[], with --all-landed as the counted expansion knob. This also covers the
|
|
1401
|
+
# previously-silent main-home landed truncation.
|
|
1402
|
+
test_landed_bounded_and_disclosed() {
|
|
1403
|
+
local home mate fakebin json i expected actual
|
|
1404
|
+
home=$(make_home mate-landed-caps); write_fixture "$home"
|
|
1405
|
+
mate=$(fixture_mate_home "$home")
|
|
1406
|
+
{
|
|
1407
|
+
printf '## In flight\n'
|
|
1408
|
+
printf '%s\n\n' '- [ ] mate - Decide subscription order (repo: firstmate) (kind: ship) (since 2026-07-11)'
|
|
1409
|
+
printf '## Done\n'
|
|
1410
|
+
} > "$mate/data/backlog.md"
|
|
1411
|
+
i=1
|
|
1412
|
+
while [ "$i" -le 12 ]; do
|
|
1413
|
+
printf -- '- [x] mate-landed-%02d - Secondmate fix %02d (repo: firstmate) (kind: ship) (merged 2026-06-%02d)\n' \
|
|
1414
|
+
"$i" "$i" "$((13 - i))" >> "$mate/data/backlog.md"
|
|
1415
|
+
i=$((i + 1))
|
|
1416
|
+
done
|
|
1417
|
+
fakebin=$(make_fakebin "$home")
|
|
1418
|
+
json=$(FM_BEARINGS_LANDED=20 run "$home" "$fakebin" --json)
|
|
1419
|
+
printf '%s' "$json" | jq -e '
|
|
1420
|
+
([.landed[].id | select(startswith("mate-landed-"))] | length) == 10
|
|
1421
|
+
and ([.omitted[].surface] | any(test("snapshot layer")))
|
|
1422
|
+
' >/dev/null || fail "default landed path must retain and disclose the snapshot per-home cap: $json"
|
|
1423
|
+
json=$(FM_BEARINGS_LANDED=1 run "$home" "$fakebin" --json --all-landed)
|
|
1424
|
+
expected=done-a
|
|
1425
|
+
i=1
|
|
1426
|
+
while [ "$i" -le 12 ]; do
|
|
1427
|
+
expected="$expected
|
|
1428
|
+
$(printf 'mate-landed-%02d' "$i")"
|
|
1429
|
+
i=$((i + 1))
|
|
1430
|
+
done
|
|
1431
|
+
expected=$(printf '%s\n' "$expected" | LC_ALL=C sort)
|
|
1432
|
+
actual=$(printf '%s' "$json" | jq -r '.landed[].id' | LC_ALL=C sort)
|
|
1433
|
+
[ "$actual" = "$expected" ] || fail "--all-landed returned wrong identities: $actual"
|
|
1434
|
+
printf '%s' "$json" | jq -e '
|
|
1435
|
+
(.landed | length) == 13
|
|
1436
|
+
and ([.omitted[].surface] | any(test("landed|snapshot layer")) | not)
|
|
1437
|
+
' >/dev/null || fail "--all-landed must reveal the exact full landed set: $json"
|
|
1438
|
+
pass "landed stays bounded with per-home + overall caps and omitted[] disclosure"
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
# Bearings projects authoritative structured state rather than inventing return
|
|
1442
|
+
# policy. A live blocked child remains a live in-flight record with state=blocked
|
|
1443
|
+
# and an open blocker; it must never be converted into a queued `gates` record.
|
|
1444
|
+
# The return-catch-up owner prevents this state from reaching ordinary rendering
|
|
1445
|
+
# during an away return, while this test pins Bearings' own projection boundary.
|
|
1446
|
+
test_live_blocker_is_not_charted_queue_work() {
|
|
1447
|
+
local home fakebin json
|
|
1448
|
+
home=$(make_home live-blocker); write_fixture "$home"
|
|
1449
|
+
printf 'blocked [key=synthetic-dependency]: firstmate can refresh the synthetic token\n' > "$home/state/ship-task.status"
|
|
1450
|
+
record_claude_state "$home/state" ship-task idle
|
|
1451
|
+
fakebin=$(make_fakebin "$home")
|
|
1452
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1453
|
+
printf '%s' "$json" | jq -e '
|
|
1454
|
+
(.in_flight | any(.[]; .id == "ship-task" and .state == "blocked"))
|
|
1455
|
+
and (.decisions_open | any(.[]; .id == "ship-task") | not)
|
|
1456
|
+
and (.gates | any(.[]; .id == "ship-task") | not)
|
|
1457
|
+
' >/dev/null || fail "live blocked work was projected as queued/deferred work: $json"
|
|
1458
|
+
pass "Bearings keeps a live blocker in structured live state and never converts it to Charted Next queue work"
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
# Captain's Call is populated only from the durable keyed open-decision set. The
|
|
1462
|
+
# anti-leak guard: action-free highlights - a working task, a completed scout,
|
|
1463
|
+
# queued/gated items, landed work - must never surface as an open decision, so they
|
|
1464
|
+
# cannot leak into Captain's Call. The standard fixture has exactly one genuine open
|
|
1465
|
+
# decision (the secondmate's structured captain hold).
|
|
1466
|
+
test_captains_call_anti_leak() {
|
|
1467
|
+
local home fakebin json canonical
|
|
1468
|
+
home=$(make_home anti-leak); write_fixture "$home"
|
|
1469
|
+
fakebin=$(make_fakebin "$home")
|
|
1470
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1471
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" "$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1472
|
+
jq -n -e --argjson bearings "$json" --argjson canonical "$canonical" '
|
|
1473
|
+
([$bearings.decisions_open[].id] == ["mate/mate-decision-race"])
|
|
1474
|
+
and ($canonical.secondmate_current.records[] | select(.id == "mate")
|
|
1475
|
+
| (.decisions_open | any(.source == "status"))
|
|
1476
|
+
and (.decisions_open | any(.source == "backlog")))
|
|
1477
|
+
and ([$bearings.decisions_open[].id] | index("ship-task") | not)
|
|
1478
|
+
and ([$bearings.decisions_open[].id] | index("scout-x") | not)
|
|
1479
|
+
and ([$bearings.decisions_open[].id] | index("external-wait") | not)
|
|
1480
|
+
and ([$bearings.decisions_open[].id] | index("done-a") | not)
|
|
1481
|
+
and ([$bearings.decisions_open[].id] | index("mate-landed") | not)
|
|
1482
|
+
and ([$bearings.decisions_open[].id] | index("live-gate") | not)
|
|
1483
|
+
and ([$bearings.decisions_open[].id] | index("dead-gate") | not)
|
|
1484
|
+
' >/dev/null || fail "only genuine open decisions may feed Captain's Call: $json"
|
|
1485
|
+
pass "action-free items (working/done/queued/landed) do not leak into Captain's Call"
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
# R1: main-home orphan in-flight and unstructured current rows must not vanish
|
|
1489
|
+
# silently. Meta remains the sole live-work inventory; disclosure is via
|
|
1490
|
+
# main_inventory + omitted[] + a Charted Next gate line, never fake Underway.
|
|
1491
|
+
test_main_orphan_in_flight_is_disclosed_not_invented() {
|
|
1492
|
+
local home fakebin json canonical
|
|
1493
|
+
home=$(make_home main-orphan)
|
|
1494
|
+
: > "$home/data/secondmates.md"
|
|
1495
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1496
|
+
## In flight
|
|
1497
|
+
- [ ] only-orphan - Structured in flight without meta (repo: firstmate) (kind: ship) (since 2026-07-11)
|
|
1498
|
+
|
|
1499
|
+
## Queued
|
|
1500
|
+
|
|
1501
|
+
## Done
|
|
1502
|
+
EOF
|
|
1503
|
+
fakebin=$(make_fakebin "$home")
|
|
1504
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1505
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1506
|
+
printf '%s' "$canonical" | jq -e '
|
|
1507
|
+
.main_inventory.valid == false
|
|
1508
|
+
and .main_inventory.reason == "in-flight backlog item has no child metadata"
|
|
1509
|
+
and (.main_inventory.orphan_in_flight == ["only-orphan"])
|
|
1510
|
+
and .main_inventory.unstructured_current_count == 0
|
|
1511
|
+
and (.tasks | length) == 0
|
|
1512
|
+
' >/dev/null || fail "canonical main inventory missed orphan: $canonical"
|
|
1513
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1514
|
+
printf '%s' "$json" | jq -e '
|
|
1515
|
+
(.in_flight | length) == 0
|
|
1516
|
+
and ([.in_flight[].id] | index("only-orphan") | not)
|
|
1517
|
+
and ([.decisions_open[].id] | index("only-orphan") | not)
|
|
1518
|
+
and (.gates | any(.id == "(main-inventory)"
|
|
1519
|
+
and (.title | contains("in-flight backlog item has no child metadata"))))
|
|
1520
|
+
and (.omitted | any(.surface == "main in-flight backlog item(s) have no child metadata: 1"))
|
|
1521
|
+
' >/dev/null || fail "orphan in-flight was invented or not disclosed: $json"
|
|
1522
|
+
pass "main orphan in-flight stays out of Underway and is disclosed in omitted/gates"
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
test_main_unstructured_current_is_disclosed_with_structured_sibling() {
|
|
1526
|
+
local home fakebin json canonical
|
|
1527
|
+
home=$(make_home main-unstructured)
|
|
1528
|
+
: > "$home/data/secondmates.md"
|
|
1529
|
+
mkdir -p "$home/projects/structured-ship"
|
|
1530
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1531
|
+
## In flight
|
|
1532
|
+
this current row is not structured
|
|
1533
|
+
- [ ] structured-ship - Visible structured sibling (repo: firstmate) (kind: ship) (since 2026-07-11)
|
|
1534
|
+
|
|
1535
|
+
## Queued
|
|
1536
|
+
another free-form note without checkbox
|
|
1537
|
+
- [ ] structured-queued - Structured queued (repo: firstmate) (kind: ship)
|
|
1538
|
+
|
|
1539
|
+
## Done
|
|
1540
|
+
EOF
|
|
1541
|
+
fm_write_meta "$home/state/structured-ship.meta" \
|
|
1542
|
+
"window=firstmate:fm-structured-ship" \
|
|
1543
|
+
"worktree=$home/projects/structured-ship" \
|
|
1544
|
+
"project=firstmate" \
|
|
1545
|
+
"harness=codex" \
|
|
1546
|
+
"kind=ship" \
|
|
1547
|
+
"mode=no-mistakes"
|
|
1548
|
+
printf 'working: structured sibling still projects\n' > "$home/state/structured-ship.status"
|
|
1549
|
+
fakebin=$(make_fakebin "$home")
|
|
1550
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1551
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1552
|
+
printf '%s' "$canonical" | jq -e '
|
|
1553
|
+
.main_inventory.valid == false
|
|
1554
|
+
and .main_inventory.reason == "unstructured current backlog row"
|
|
1555
|
+
and .main_inventory.unstructured_current_count == 2
|
|
1556
|
+
and (.main_inventory.orphan_in_flight | length) == 0
|
|
1557
|
+
' >/dev/null || fail "canonical main inventory missed unstructured current: $canonical"
|
|
1558
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1559
|
+
printf '%s' "$json" | jq -e '
|
|
1560
|
+
([.in_flight[].id] == ["structured-ship"])
|
|
1561
|
+
and ([.gates[].id] | index("structured-queued") != null)
|
|
1562
|
+
and (.gates | any(.id == "(main-inventory)"
|
|
1563
|
+
and (.title | contains("unstructured current backlog row"))))
|
|
1564
|
+
and (.omitted | any(.surface == "main unstructured current backlog row(s): 2"))
|
|
1565
|
+
and ([.decisions_open[].id] | index("(main-inventory)") | not)
|
|
1566
|
+
' >/dev/null || fail "unstructured current not disclosed or structured sibling lost: $json"
|
|
1567
|
+
pass "main unstructured current is disclosed while structured siblings still project"
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
test_main_orphan_counterfactual_meta_clears_inventory_warning() {
|
|
1571
|
+
local home fakebin json_before json_after
|
|
1572
|
+
home=$(make_home main-orphan-counterfactual)
|
|
1573
|
+
: > "$home/data/secondmates.md"
|
|
1574
|
+
mkdir -p "$home/projects/orphan-ship"
|
|
1575
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1576
|
+
## In flight
|
|
1577
|
+
- [ ] orphan-ship - Gains meta in counterfactual (repo: firstmate) (kind: ship) (since 2026-07-11)
|
|
1578
|
+
- [ ] visible-ship - Already live (repo: firstmate) (kind: ship) (since 2026-07-11)
|
|
1579
|
+
|
|
1580
|
+
## Queued
|
|
1581
|
+
- [ ] queued-ship - Ordinary queue (repo: firstmate) (kind: ship)
|
|
1582
|
+
|
|
1583
|
+
## Done
|
|
1584
|
+
EOF
|
|
1585
|
+
fm_write_meta "$home/state/visible-ship.meta" \
|
|
1586
|
+
"window=firstmate:fm-visible-ship" \
|
|
1587
|
+
"worktree=$home/projects/orphan-ship" \
|
|
1588
|
+
"project=firstmate" \
|
|
1589
|
+
"harness=codex" \
|
|
1590
|
+
"kind=ship" \
|
|
1591
|
+
"mode=no-mistakes"
|
|
1592
|
+
printf 'working: visible sibling\n' > "$home/state/visible-ship.status"
|
|
1593
|
+
fakebin=$(make_fakebin "$home")
|
|
1594
|
+
json_before=$(run "$home" "$fakebin" --json)
|
|
1595
|
+
printf '%s' "$json_before" | jq -e '
|
|
1596
|
+
([.in_flight[].id] == ["visible-ship"])
|
|
1597
|
+
and ([.in_flight[].id] | index("orphan-ship") | not)
|
|
1598
|
+
and (.omitted | any(.surface == "main in-flight backlog item(s) have no child metadata: 1"))
|
|
1599
|
+
and (.gates | any(.id == "(main-inventory)"))
|
|
1600
|
+
and ([.gates[].id] | index("queued-ship") != null)
|
|
1601
|
+
' >/dev/null || fail "pre-meta orphan fixture failed: $json_before"
|
|
1602
|
+
fm_write_meta "$home/state/orphan-ship.meta" \
|
|
1603
|
+
"window=firstmate:fm-orphan-ship" \
|
|
1604
|
+
"worktree=$home/projects/orphan-ship" \
|
|
1605
|
+
"project=firstmate" \
|
|
1606
|
+
"harness=codex" \
|
|
1607
|
+
"kind=ship" \
|
|
1608
|
+
"mode=no-mistakes"
|
|
1609
|
+
printf 'working: orphan now has meta\n' > "$home/state/orphan-ship.status"
|
|
1610
|
+
json_after=$(run "$home" "$fakebin" --json)
|
|
1611
|
+
printf '%s' "$json_after" | jq -e '
|
|
1612
|
+
([.in_flight[].id] | sort) == ["orphan-ship", "visible-ship"]
|
|
1613
|
+
and ([.omitted[].surface] | any(test("main in-flight backlog item")) | not)
|
|
1614
|
+
and ([.gates[].id] | index("(main-inventory)") | not)
|
|
1615
|
+
and ([.decisions_open[].id] | index("orphan-ship") | not)
|
|
1616
|
+
' >/dev/null || fail "adding meta did not clear inventory warning or project orphan: $json_after"
|
|
1617
|
+
pass "counterfactual meta clears main inventory warning and projects the live task"
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
test_mixed_secondmate_roles_partial_state_and_captain_readiness() {
|
|
1621
|
+
local home fakebin hibit wheel sshhip ha canonical json
|
|
1622
|
+
home=$(make_home mixed-domain-regressions)
|
|
1623
|
+
: > "$home/data/secondmates.md"
|
|
1624
|
+
hibit="$TMP_ROOT/mixed-hibit-home"
|
|
1625
|
+
wheel="$TMP_ROOT/mixed-wheel-home"
|
|
1626
|
+
sshhip="$TMP_ROOT/mixed-sshhip-home"
|
|
1627
|
+
ha="$TMP_ROOT/mixed-ha-home"
|
|
1628
|
+
make_valid_secondmate_home hibit "$hibit"
|
|
1629
|
+
make_valid_secondmate_home wheel "$wheel"
|
|
1630
|
+
make_valid_secondmate_home sshhip "$sshhip"
|
|
1631
|
+
make_valid_secondmate_home home-assistant "$ha"
|
|
1632
|
+
append_secondmate_registry "$home" hibit "$hibit"
|
|
1633
|
+
append_secondmate_registry "$home" wheel "$wheel"
|
|
1634
|
+
append_secondmate_registry "$home" sshhip "$sshhip"
|
|
1635
|
+
append_secondmate_registry "$home" home-assistant "$ha"
|
|
1636
|
+
|
|
1637
|
+
mkdir -p "$hibit/projects/worker" "$wheel/projects/worker" "$sshhip/projects/child" "$ha/projects/prep"
|
|
1638
|
+
cat > "$hibit/data/backlog.md" <<'EOF'
|
|
1639
|
+
## In flight
|
|
1640
|
+
- [ ] dogfood-program - Long-lived dogfood program (repo: hibit) (kind: program)
|
|
1641
|
+
- [ ] hibit-worker - Finalize progress (repo: hibit) (kind: ship)
|
|
1642
|
+
|
|
1643
|
+
## Queued
|
|
1644
|
+
|
|
1645
|
+
## Done
|
|
1646
|
+
EOF
|
|
1647
|
+
fm_write_meta "$hibit/state/hibit-worker.meta" \
|
|
1648
|
+
"window=firstmate:fm-hibit-worker" "worktree=$hibit/projects/worker" "project=hibit" \
|
|
1649
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
1650
|
+
record_claude_state "$hibit/state" hibit-worker busy
|
|
1651
|
+
printf 'working: finalizing progress\n' > "$hibit/state/hibit-worker.status"
|
|
1652
|
+
|
|
1653
|
+
cat > "$wheel/data/backlog.md" <<'EOF'
|
|
1654
|
+
## In flight
|
|
1655
|
+
- [ ] production-observation - Observe production (repo: wheelhouse) (kind: scout) (hold: documented no live worker) (hold-kind: external)
|
|
1656
|
+
- [ ] wheel-worker - Initial triage (repo: wheelhouse) (kind: ship)
|
|
1657
|
+
|
|
1658
|
+
## Queued
|
|
1659
|
+
|
|
1660
|
+
## Done
|
|
1661
|
+
EOF
|
|
1662
|
+
fm_write_meta "$wheel/state/wheel-worker.meta" \
|
|
1663
|
+
"window=firstmate:fm-wheel-worker" "worktree=$wheel/projects/worker" "project=wheelhouse" \
|
|
1664
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
1665
|
+
record_claude_state "$wheel/state" wheel-worker busy
|
|
1666
|
+
printf 'working: active validation\n' > "$wheel/state/wheel-worker.status"
|
|
1667
|
+
|
|
1668
|
+
cat > "$sshhip/data/backlog.md" <<'EOF'
|
|
1669
|
+
## In flight
|
|
1670
|
+
- [ ] unreadable-child - Submit App Store build (repo: sshhip) (kind: ship)
|
|
1671
|
+
|
|
1672
|
+
## Queued
|
|
1673
|
+
- [ ] reviewer-decision - Choose reviewer remediation (repo: sshhip) (kind: captain) (hold: choose reviewer remediation A or B) (hold-kind: captain)
|
|
1674
|
+
|
|
1675
|
+
## Done
|
|
1676
|
+
- [x] prior-release - Prior release (repo: sshhip) (kind: ship) (done 2026-07-21)
|
|
1677
|
+
EOF
|
|
1678
|
+
fm_write_meta "$sshhip/state/unreadable-child.meta" \
|
|
1679
|
+
"window=firstmate:dead-sshhip-child" "worktree=$sshhip/projects/child" "project=sshhip" \
|
|
1680
|
+
"harness=codex" "kind=ship" "mode=no-mistakes"
|
|
1681
|
+
|
|
1682
|
+
cat > "$ha/data/backlog.md" <<'EOF'
|
|
1683
|
+
## In flight
|
|
1684
|
+
- [ ] prep - Prepare canary (repo: home-assistant) (kind: ship)
|
|
1685
|
+
|
|
1686
|
+
## Queued
|
|
1687
|
+
- [ ] security - Security review (repo: home-assistant) (kind: ship)
|
|
1688
|
+
- [ ] captain-run - Run captain canary blocked-by: prep blocked-by: security (repo: home-assistant) (kind: captain) (hold: captain runs canary) (hold-kind: captain)
|
|
1689
|
+
|
|
1690
|
+
## Done
|
|
1691
|
+
EOF
|
|
1692
|
+
fm_write_meta "$ha/state/prep.meta" \
|
|
1693
|
+
"window=firstmate:fm-prep" "worktree=$ha/projects/prep" "project=home-assistant" \
|
|
1694
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
1695
|
+
record_claude_state "$ha/state" prep busy
|
|
1696
|
+
printf 'working: preparing canary\n' > "$ha/state/prep.status"
|
|
1697
|
+
|
|
1698
|
+
fakebin=$(make_fakebin "$home")
|
|
1699
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1700
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1701
|
+
printf '%s' "$canonical" | jq -e '
|
|
1702
|
+
(.secondmate_current.records[] | select(.id == "hibit")
|
|
1703
|
+
| .current.state == "active_child_work"
|
|
1704
|
+
and [.active_children[].id] == ["hibit-worker"]
|
|
1705
|
+
and ([.endpoints[].id] | index("dogfood-program") | not))
|
|
1706
|
+
and (.secondmate_current.records[] | select(.id == "wheel")
|
|
1707
|
+
| .current.state == "active_child_work"
|
|
1708
|
+
and [.active_children[].id] == ["wheel-worker"]
|
|
1709
|
+
and [.queued[].id] == ["production-observation"]
|
|
1710
|
+
and [.holds[].id] == ["production-observation"])
|
|
1711
|
+
and (.secondmate_current.records[] | select(.id == "sshhip")
|
|
1712
|
+
| .current.state == "unknown"
|
|
1713
|
+
and (.current.reason | contains("child current state unavailable: unreadable-child"))
|
|
1714
|
+
and .provenance.selected == "structured-home"
|
|
1715
|
+
and .provenance.summary_valid == false
|
|
1716
|
+
and .provenance.trust == "partial-structured"
|
|
1717
|
+
and .invalidity == {kind:"child_current_unavailable",ids:["unreadable-child"]}
|
|
1718
|
+
and [.decisions_open[].id] == ["reviewer-decision"]
|
|
1719
|
+
and [.holds[].id] == ["reviewer-decision"]
|
|
1720
|
+
and [.queued[].id] == ["reviewer-decision"]
|
|
1721
|
+
and [.landed[].id] == ["prior-release"]
|
|
1722
|
+
and [.endpoints[].id] == ["unreadable-child"]
|
|
1723
|
+
and .counts.decisions_open == 1
|
|
1724
|
+
and .counts.holds == 1
|
|
1725
|
+
and .counts.queued == 1
|
|
1726
|
+
and .counts.landed == 1
|
|
1727
|
+
and .counts.endpoints == 1)
|
|
1728
|
+
and (.secondmate_landed.partial | length) == 1
|
|
1729
|
+
and (.secondmate_landed.partial[0] | endswith("/mixed-sshhip-home"))
|
|
1730
|
+
and (.secondmate_landed.unreadable | length) == 0
|
|
1731
|
+
and (.secondmate_current.records[] | select(.id == "home-assistant")
|
|
1732
|
+
| .current.state == "active_child_work"
|
|
1733
|
+
and .decisions_open == []
|
|
1734
|
+
and [.active_children[].id] == ["prep"]
|
|
1735
|
+
and (.queued[] | select(.id == "captain-run")
|
|
1736
|
+
| .blocked_by_ids == ["prep", "security"]
|
|
1737
|
+
and .unresolved_blocker_ids == ["prep", "security"]
|
|
1738
|
+
and .captain_actionable == false))
|
|
1739
|
+
' >/dev/null || fail "canonical mixed-domain classification was wrong: $canonical"
|
|
1740
|
+
json=$(run "$home" "$fakebin" --json --fields bodies --all-landed)
|
|
1741
|
+
printf '%s' "$json" | jq -e '
|
|
1742
|
+
([.in_flight[].id] | sort) == ["hibit", "home-assistant", "wheel"]
|
|
1743
|
+
and (.decisions_open | any(.id == "sshhip/reviewer-decision"))
|
|
1744
|
+
and (.decisions_open | any(.id == "home-assistant/captain-run") | not)
|
|
1745
|
+
and (.gates | any(.id == "production-observation" and .owner == "wheel"
|
|
1746
|
+
and .reason == "documented no live worker"))
|
|
1747
|
+
and (.gates | any(.id == "captain-run" and .owner == "home-assistant"
|
|
1748
|
+
and .blocked_by == "prep,security"))
|
|
1749
|
+
and (.secondmates | any(.id == "sshhip" and .state == "unknown"
|
|
1750
|
+
and (.reason | contains("unreadable-child"))))
|
|
1751
|
+
' >/dev/null || fail "end-to-end mixed-domain projection was wrong: $json"
|
|
1752
|
+
|
|
1753
|
+
sed '/unreadable-child/a\
|
|
1754
|
+
- [ ] ordinary-orphan - Unowned release task (repo: sshhip) (kind: ship)' \
|
|
1755
|
+
"$sshhip/data/backlog.md" > "$sshhip/data/backlog.next"
|
|
1756
|
+
mv "$sshhip/data/backlog.next" "$sshhip/data/backlog.md"
|
|
1757
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1758
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1759
|
+
printf '%s' "$canonical" | jq -e '
|
|
1760
|
+
.secondmate_current.records[] | select(.id == "sshhip")
|
|
1761
|
+
| .current.state == "unknown"
|
|
1762
|
+
and (.current.reason | contains("in-flight backlog item has no child metadata: ordinary-orphan"))
|
|
1763
|
+
and .provenance.selected == "structured-home"
|
|
1764
|
+
and .provenance.trust == "partial-structured"
|
|
1765
|
+
and .invalidity == {kind:"orphan_in_flight",ids:["ordinary-orphan"]}
|
|
1766
|
+
and [.decisions_open[].id] == ["reviewer-decision"]
|
|
1767
|
+
and [.holds[].id] == ["reviewer-decision"]
|
|
1768
|
+
and [.queued[].id] == ["reviewer-decision"]
|
|
1769
|
+
and [.landed[].id] == ["prior-release"]
|
|
1770
|
+
and [.endpoints[].id] == ["unreadable-child"]
|
|
1771
|
+
' >/dev/null || fail "an ordinary orphan discarded a readable home alongside an unknown child: $canonical"
|
|
1772
|
+
sed '/ordinary-orphan/d' "$sshhip/data/backlog.md" > "$sshhip/data/backlog.next"
|
|
1773
|
+
mv "$sshhip/data/backlog.next" "$sshhip/data/backlog.md"
|
|
1774
|
+
|
|
1775
|
+
sed '/unreadable-child/d' "$sshhip/data/backlog.md" > "$sshhip/data/backlog.next"
|
|
1776
|
+
mv "$sshhip/data/backlog.next" "$sshhip/data/backlog.md"
|
|
1777
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1778
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1779
|
+
printf '%s' "$canonical" | jq -e '
|
|
1780
|
+
.secondmate_current.records[] | select(.id == "sshhip")
|
|
1781
|
+
| .current.state == "unknown"
|
|
1782
|
+
and (.current.reason | contains("live child state has no in-flight backlog item: unreadable-child=unknown"))
|
|
1783
|
+
and .provenance.selected == "structured-home"
|
|
1784
|
+
and .provenance.trust == "partial-structured"
|
|
1785
|
+
and .invalidity == {kind:"unowned_current",ids:["unreadable-child"]}
|
|
1786
|
+
and [.decisions_open[].id] == ["reviewer-decision"]
|
|
1787
|
+
and [.holds[].id] == ["reviewer-decision"]
|
|
1788
|
+
and [.queued[].id] == ["reviewer-decision"]
|
|
1789
|
+
and [.landed[].id] == ["prior-release"]
|
|
1790
|
+
' >/dev/null || fail "an unowned unknown child discarded the readable home: $canonical"
|
|
1791
|
+
sed '/## In flight/a\
|
|
1792
|
+
- [ ] unreadable-child - Submit App Store build (repo: sshhip) (kind: ship)' \
|
|
1793
|
+
"$sshhip/data/backlog.md" > "$sshhip/data/backlog.next"
|
|
1794
|
+
mv "$sshhip/data/backlog.next" "$sshhip/data/backlog.md"
|
|
1795
|
+
|
|
1796
|
+
fm_write_meta "$wheel/state/production-observation.meta" \
|
|
1797
|
+
"window=firstmate:fm-production-observation" "worktree=$wheel/projects/worker" "project=wheelhouse" \
|
|
1798
|
+
"harness=claude" "kind=scout" "mode=scout"
|
|
1799
|
+
record_claude_state "$wheel/state" production-observation idle
|
|
1800
|
+
printf 'paused: observation is deliberately held\n' > "$wheel/state/production-observation.status"
|
|
1801
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1802
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1803
|
+
printf '%s' "$canonical" | jq -e '
|
|
1804
|
+
.secondmate_current.records[] | select(.id == "wheel")
|
|
1805
|
+
| ([.queued[] | select(.id == "production-observation")] | length) == 1
|
|
1806
|
+
and ([.holds[] | select(.id == "production-observation")] | length) == 1
|
|
1807
|
+
and ([.endpoints[] | select(.id == "production-observation")] | length) == 1
|
|
1808
|
+
' >/dev/null || fail "held metadata plus a real child duplicated or discarded the record: $canonical"
|
|
1809
|
+
|
|
1810
|
+
fm_write_meta "$sshhip/state/unreadable-child.meta" \
|
|
1811
|
+
"window=firstmate:fm-unreadable-child" "worktree=$sshhip/projects/child" "project=sshhip" \
|
|
1812
|
+
"harness=claude" "kind=ship" "mode=no-mistakes"
|
|
1813
|
+
record_claude_state "$sshhip/state" unreadable-child busy
|
|
1814
|
+
printf 'working: app store submission restored\n' > "$sshhip/state/unreadable-child.status"
|
|
1815
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1816
|
+
printf '%s' "$json" | jq -e '
|
|
1817
|
+
(.secondmates | any(.id == "sshhip" and .state == "captain_decision" and .reason == "-"))
|
|
1818
|
+
and ([.decisions_open[] | select(.id == "sshhip/reviewer-decision")] | length) == 1
|
|
1819
|
+
' >/dev/null || fail "restoring the SSHHIP child did not clear only its narrow warning: $json"
|
|
1820
|
+
|
|
1821
|
+
cat > "$ha/data/backlog.md" <<'EOF'
|
|
1822
|
+
## In flight
|
|
1823
|
+
|
|
1824
|
+
## Queued
|
|
1825
|
+
- [ ] security - Security review (repo: home-assistant) (kind: ship)
|
|
1826
|
+
- [ ] captain-run - Run captain canary blocked-by: prep blocked-by: security (repo: home-assistant) (kind: captain) (hold: captain runs canary) (hold-kind: captain)
|
|
1827
|
+
|
|
1828
|
+
## Done
|
|
1829
|
+
- [x] prep - Prepare canary (repo: home-assistant) (kind: ship) (done 2026-07-22)
|
|
1830
|
+
EOF
|
|
1831
|
+
rm "$ha/state/prep.meta" "$ha/state/prep.status"
|
|
1832
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1833
|
+
printf '%s' "$json" | jq -e '
|
|
1834
|
+
(.decisions_open | any(.id == "home-assistant/captain-run") | not)
|
|
1835
|
+
and (.gates | any(.id == "captain-run" and .owner == "home-assistant" and .blocked_by == "security"))
|
|
1836
|
+
' >/dev/null || fail "one remaining Home Assistant blocker became actionable: $json"
|
|
1837
|
+
|
|
1838
|
+
cat > "$ha/data/backlog.md" <<'EOF'
|
|
1839
|
+
## In flight
|
|
1840
|
+
|
|
1841
|
+
## Queued
|
|
1842
|
+
- [ ] captain-run - Run captain canary blocked-by: prep blocked-by: security (repo: home-assistant) (kind: captain) (hold: captain runs canary) (hold-kind: captain)
|
|
1843
|
+
|
|
1844
|
+
## Done
|
|
1845
|
+
- [x] prep - Prepare canary (repo: home-assistant) (kind: ship) (done 2026-07-22)
|
|
1846
|
+
- [x] security - Security review (repo: home-assistant) (kind: ship) (done 2026-07-22)
|
|
1847
|
+
EOF
|
|
1848
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1849
|
+
printf '%s' "$json" | jq -e '
|
|
1850
|
+
([.decisions_open[] | select(.id == "home-assistant/captain-run")] | length) == 1
|
|
1851
|
+
and (.gates | any(.id == "captain-run" and .owner == "home-assistant") | not)
|
|
1852
|
+
' >/dev/null || fail "zero Home Assistant blockers did not yield exactly one captain action: $json"
|
|
1853
|
+
|
|
1854
|
+
sed 's/blocked-by: security/blocked-by: missing/' "$ha/data/backlog.md" > "$ha/data/backlog.next"
|
|
1855
|
+
mv "$ha/data/backlog.next" "$ha/data/backlog.md"
|
|
1856
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1857
|
+
printf '%s' "$json" | jq -e '
|
|
1858
|
+
(.decisions_open | any(.id == "home-assistant/captain-run") | not)
|
|
1859
|
+
and (.gates | any(.id == "captain-run" and .owner == "home-assistant" and .blocked_by == "missing"))
|
|
1860
|
+
' >/dev/null || fail "a missing Home Assistant blocker was treated as Done: $json"
|
|
1861
|
+
|
|
1862
|
+
sed 's/(kind: program)/(kind: mystery)/' "$hibit/data/backlog.md" > "$hibit/data/backlog.next"
|
|
1863
|
+
mv "$hibit/data/backlog.next" "$hibit/data/backlog.md"
|
|
1864
|
+
canonical=$(PATH="$fakebin:$PATH" FM_HOME="$home" FM_SNAPSHOT_NOW=2026-07-11T18:00:00Z \
|
|
1865
|
+
"$ROOT/bin/fm-fleet-snapshot.sh" --json)
|
|
1866
|
+
printf '%s' "$canonical" | jq -e '
|
|
1867
|
+
.secondmate_current.records[] | select(.id == "hibit")
|
|
1868
|
+
| .current.state == "active_child_work"
|
|
1869
|
+
and (.current.reason | contains("in-flight backlog item has no child metadata: dogfood-program"))
|
|
1870
|
+
and .provenance.selected == "structured-home"
|
|
1871
|
+
and .provenance.trust == "partial-structured"
|
|
1872
|
+
and .invalidity == {kind:"orphan_in_flight",ids:["dogfood-program"]}
|
|
1873
|
+
and [.active_children[].id] == ["hibit-worker"]
|
|
1874
|
+
and [.endpoints[].id] == ["hibit-worker"]
|
|
1875
|
+
' >/dev/null || fail "an unrecognized worker kind hid the home's live work: $canonical"
|
|
1876
|
+
pass "mixed secondmate roles, partial state, and captain readiness project independently"
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
test_main_captain_readiness_matches_secondmate_projection() {
|
|
1880
|
+
local home fakebin json
|
|
1881
|
+
home=$(make_home main-captain-readiness)
|
|
1882
|
+
: > "$home/data/secondmates.md"
|
|
1883
|
+
mkdir -p "$home/projects/prep" "$home/projects/observation"
|
|
1884
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1885
|
+
## In flight
|
|
1886
|
+
- [ ] observation - Held observation (repo: firstmate) (kind: scout) (hold: watch production) (hold-kind: external)
|
|
1887
|
+
- [ ] prep - Prepare canary (repo: firstmate) (kind: ship)
|
|
1888
|
+
|
|
1889
|
+
## Queued
|
|
1890
|
+
- [ ] review - Security review (repo: firstmate) (kind: ship)
|
|
1891
|
+
- [ ] captain-run - Run captain canary blocked-by: prep blocked-by: review (repo: firstmate) (kind: captain) (hold: captain runs canary) (hold-kind: captain)
|
|
1892
|
+
|
|
1893
|
+
## Done
|
|
1894
|
+
EOF
|
|
1895
|
+
fm_write_meta "$home/state/prep.meta" \
|
|
1896
|
+
"window=firstmate:fm-prep" "worktree=$home/projects/prep" "project=firstmate" \
|
|
1897
|
+
"harness=codex" "kind=ship" "mode=no-mistakes"
|
|
1898
|
+
printf 'working: preparing main canary\n' > "$home/state/prep.status"
|
|
1899
|
+
fm_write_meta "$home/state/observation.meta" \
|
|
1900
|
+
"window=firstmate:fm-observation" "worktree=$home/projects/observation" "project=firstmate" \
|
|
1901
|
+
"harness=codex" "kind=scout" "mode=scout"
|
|
1902
|
+
printf 'paused: observation is deliberately held\n' > "$home/state/observation.status"
|
|
1903
|
+
fakebin=$(make_fakebin "$home")
|
|
1904
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1905
|
+
printf '%s' "$json" | jq -e '
|
|
1906
|
+
(.in_flight | any(.id == "prep"))
|
|
1907
|
+
and (.in_flight | any(.id == "observation") | not)
|
|
1908
|
+
and ([.gates[] | select(.id == "observation" and .reason == "watch production")] | length) == 1
|
|
1909
|
+
and (.decisions_open | any(.id == "captain-run") | not)
|
|
1910
|
+
and (.gates | any(.id == "captain-run" and .blocked_by == "prep,review"))
|
|
1911
|
+
' >/dev/null || fail "main blocked captain action or held-child projection was wrong: $json"
|
|
1912
|
+
|
|
1913
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1914
|
+
## In flight
|
|
1915
|
+
|
|
1916
|
+
## Queued
|
|
1917
|
+
- [ ] review - Security review (repo: firstmate) (kind: ship)
|
|
1918
|
+
- [ ] captain-run - Run captain canary blocked-by: prep blocked-by: review (repo: firstmate) (kind: captain) (hold: captain runs canary) (hold-kind: captain)
|
|
1919
|
+
|
|
1920
|
+
## Done
|
|
1921
|
+
- [x] prep - Prepare canary (repo: firstmate) (kind: ship) (done 2026-07-22)
|
|
1922
|
+
EOF
|
|
1923
|
+
rm "$home/state/prep.meta" "$home/state/prep.status" \
|
|
1924
|
+
"$home/state/observation.meta" "$home/state/observation.status"
|
|
1925
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1926
|
+
printf '%s' "$json" | jq -e '
|
|
1927
|
+
(.decisions_open | any(.id == "captain-run") | not)
|
|
1928
|
+
and (.gates | any(.id == "captain-run" and .blocked_by == "review"))
|
|
1929
|
+
' >/dev/null || fail "main one-blocker captain action became premature: $json"
|
|
1930
|
+
|
|
1931
|
+
cat > "$home/data/backlog.md" <<'EOF'
|
|
1932
|
+
## In flight
|
|
1933
|
+
|
|
1934
|
+
## Queued
|
|
1935
|
+
- [ ] captain-run - Run captain canary blocked-by: prep blocked-by: review (repo: firstmate) (kind: captain) (hold: captain runs canary) (hold-kind: captain)
|
|
1936
|
+
|
|
1937
|
+
## Done
|
|
1938
|
+
- [x] prep - Prepare canary (repo: firstmate) (kind: ship) (done 2026-07-22)
|
|
1939
|
+
- [x] review - Security review (repo: firstmate) (kind: ship) (done 2026-07-22)
|
|
1940
|
+
EOF
|
|
1941
|
+
json=$(run "$home" "$fakebin" --json)
|
|
1942
|
+
printf '%s' "$json" | jq -e '
|
|
1943
|
+
([.decisions_open[] | select(.id == "captain-run")] | length) == 1
|
|
1944
|
+
and (.gates | any(.id == "captain-run") | not)
|
|
1945
|
+
' >/dev/null || fail "main zero-blocker captain action was not projected exactly once: $json"
|
|
1946
|
+
pass "main and secondmate captain actionability use the same blocker readiness"
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
test_domain_alpha_stale_parent_event_does_not_become_current_work
|
|
1950
|
+
test_gnu_stat_uses_file_formats_without_bsd_fallback_pollution
|
|
1951
|
+
test_parent_activity_evidence_is_bounded_and_disclosed
|
|
1952
|
+
test_active_child_overrides_old_parent_event
|
|
1953
|
+
test_structured_child_decision_reaches_captains_call
|
|
1954
|
+
test_bad_secondmate_homes_never_revive_parent_work
|
|
1955
|
+
test_oversized_secondmate_summary_stays_strict_unknown
|
|
1956
|
+
test_secondmate_and_child_bounds_are_disclosed
|
|
1957
|
+
test_parent_decision_is_untrusted_contradiction_only
|
|
1958
|
+
test_parent_evidence_reconciles_by_verb_and_key
|
|
1959
|
+
test_nonprogressing_child_states_are_explicit
|
|
1960
|
+
test_registry_unavailability_and_bounds_are_explicit
|
|
1961
|
+
test_current_landed_baseline_is_repeatable_and_prior_report_independent
|
|
1962
|
+
test_default_is_bounded_and_local_only
|
|
1963
|
+
test_toon_json_parity
|
|
1964
|
+
test_landed_includes_secondmate_home_merges
|
|
1965
|
+
test_landed_default_balances_dominant_and_sparse_homes
|
|
1966
|
+
test_landed_default_refills_capacity_after_sparse_homes_exhaust
|
|
1967
|
+
test_landed_default_uses_deterministic_home_order_when_homes_exceed_cap
|
|
1968
|
+
test_landed_default_preserves_internal_order_for_ties
|
|
1969
|
+
test_landed_default_handles_no_landed_items
|
|
1970
|
+
test_all_landed_keeps_complete_global_order
|
|
1971
|
+
test_landed_bounded_and_disclosed
|
|
1972
|
+
test_live_blocker_is_not_charted_queue_work
|
|
1973
|
+
test_captains_call_anti_leak
|
|
1974
|
+
test_main_orphan_in_flight_is_disclosed_not_invented
|
|
1975
|
+
test_main_unstructured_current_is_disclosed_with_structured_sibling
|
|
1976
|
+
test_main_orphan_counterfactual_meta_clears_inventory_warning
|
|
1977
|
+
test_mixed_secondmate_roles_partial_state_and_captain_readiness
|
|
1978
|
+
test_main_captain_readiness_matches_secondmate_projection
|
|
1979
|
+
test_completed_scout_report_not_pending
|
|
1980
|
+
test_open_decision_surfaces_end_to_end
|
|
1981
|
+
test_report_pointers_surface
|
|
1982
|
+
test_superseded_queued_item_dropped_by_default
|
|
1983
|
+
test_include_prs_is_the_only_fetch_path
|
|
1984
|
+
test_partial_github_failure_degrades
|
|
1985
|
+
test_perl_fallback_bounds_github_call
|
|
1986
|
+
test_section_caps_and_expansion_flags
|
|
1987
|
+
test_collapsed_captain_call_deferral_and_landed
|
|
1988
|
+
test_pr_repository_cap_and_expansion
|
|
1989
|
+
test_per_repository_pr_cap_is_disclosed
|
|
1990
|
+
test_projection_and_toon_fail_closed
|