@zerwiz/ymir 0.1.3 → 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 -39
- 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,1610 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Behavior tests for bin/fm-crew-state.sh - the deterministic crew-current-state
|
|
3
|
+
# helper.
|
|
4
|
+
#
|
|
5
|
+
# The status file (state/<id>.status) is a best-effort append-only EVENT LOG, so
|
|
6
|
+
# `tail -1` of it reports the last event, not the current state. fm-crew-state
|
|
7
|
+
# reads the AUTHORITATIVE source (a matching no-mistakes run-step, else the
|
|
8
|
+
# semantic busy-state contract) and reconciles the possibly-stale log against it. These
|
|
9
|
+
# cases pin every branch of that logic, hermetically, over real throwaway git
|
|
10
|
+
# repos with a fake `no-mistakes` (run-step source) and a fake `tmux` (pane
|
|
11
|
+
# source):
|
|
12
|
+
# (a) active run-step is authoritative -> run-step
|
|
13
|
+
# (b) needs-decision/blocked log + resumed run = SUPERSEDED -> run-step
|
|
14
|
+
# (c) genuine parked run + needs-decision log = NOT superseded -> run-step
|
|
15
|
+
# (d) terminal run-step (passed/failed) is authoritative -> run-step
|
|
16
|
+
# (e) cross-branch attribution: this branch's own run found via list lookup
|
|
17
|
+
# (f) no run + semantic busy -> pane
|
|
18
|
+
# (g) no run + semantic idle falls to the status-log verb -> status-log
|
|
19
|
+
# (h) dead pane: no run -> unknown/none; with a run -> run-step (not the shell)
|
|
20
|
+
# (i) kind=scout skips the run lookup -> pane/status-log
|
|
21
|
+
# (j) torn-down worktree / missing meta -> unknown/none
|
|
22
|
+
# (k) crew_is_provably_working end-to-end over the REAL helper (not a canned
|
|
23
|
+
# fake fm-crew-state.sh verdict): cross-branch attribution via the runs
|
|
24
|
+
# list -> absorbed; genuinely no run anywhere + idle pane -> surfaced.
|
|
25
|
+
# This is the direct regression pair for the 2026-07-02 herdr incident,
|
|
26
|
+
# proving the watcher's own absorb-only-when-provably-working predicate
|
|
27
|
+
# benefits from the fix in both directions.
|
|
28
|
+
set -u
|
|
29
|
+
|
|
30
|
+
# shellcheck source=tests/lib.sh
|
|
31
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
32
|
+
# shellcheck source=/dev/null
|
|
33
|
+
. "$ROOT/bin/fm-classify-lib.sh"
|
|
34
|
+
|
|
35
|
+
CREW_STATE="$ROOT/bin/fm-crew-state.sh"
|
|
36
|
+
TMP_ROOT=$(fm_test_tmproot fm-crew-state)
|
|
37
|
+
fm_git_identity fmtest fmtest@example.invalid
|
|
38
|
+
|
|
39
|
+
# A real git repo checked out on <branch>, so the helper's branch attribution
|
|
40
|
+
# (git symbolic-ref) resolves like it would for a live crew worktree.
|
|
41
|
+
make_repo_on_branch() { # <dir> <branch>
|
|
42
|
+
local dir=$1 branch=$2
|
|
43
|
+
mkdir -p "$dir"
|
|
44
|
+
git -C "$dir" init -q
|
|
45
|
+
git -C "$dir" commit -q --allow-empty -m init
|
|
46
|
+
git -C "$dir" checkout -q -b "$branch"
|
|
47
|
+
# Real worktree HEAD for run head-binding (fixtures read FM_FAKE_RUN_HEAD).
|
|
48
|
+
FM_FAKE_RUN_HEAD=$(git -C "$dir" rev-parse HEAD)
|
|
49
|
+
export FM_FAKE_RUN_HEAD
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# A fakebin with a fake `no-mistakes` (serves the env-driven run output) and a
|
|
53
|
+
# fake `tmux` (serves a busy or idle pane). The fake no-mistakes mirrors the real
|
|
54
|
+
# command surface the helper uses: `axi status`, `axi status --run <id>` (the
|
|
55
|
+
# `axi` surface - no runs-listing subcommand exists under it, verified against
|
|
56
|
+
# the real CLI), and the actual top-level run-listing command, `no-mistakes
|
|
57
|
+
# runs --limit N`, which is plain text - no run id, no quoting - serving
|
|
58
|
+
# FM_FAKE_RUNS_LIST verbatim.
|
|
59
|
+
make_fakebin() { # <dir> -> echoes fakebin path
|
|
60
|
+
local dir=$1 fb="$1/fakebin"
|
|
61
|
+
mkdir -p "$fb"
|
|
62
|
+
cat > "$fb/no-mistakes" <<'SH'
|
|
63
|
+
#!/usr/bin/env bash
|
|
64
|
+
set -u
|
|
65
|
+
case "${1:-}" in
|
|
66
|
+
axi)
|
|
67
|
+
shift
|
|
68
|
+
case "${1:-}" in
|
|
69
|
+
status)
|
|
70
|
+
shift
|
|
71
|
+
if [ "${1:-}" = --run ]; then printf '%s\n' "${FM_FAKE_AXI_STATUS_RUN:-}"
|
|
72
|
+
else printf '%s\n' "${FM_FAKE_AXI_STATUS:-}"; fi ;;
|
|
73
|
+
logs)
|
|
74
|
+
printf '%s\n' "${FM_FAKE_CI_LOGS:-}" ;;
|
|
75
|
+
esac
|
|
76
|
+
;;
|
|
77
|
+
runs)
|
|
78
|
+
printf '%s\n' "${FM_FAKE_RUNS_LIST:-}" ;;
|
|
79
|
+
esac
|
|
80
|
+
exit 0
|
|
81
|
+
SH
|
|
82
|
+
cat > "$fb/tmux" <<'SH'
|
|
83
|
+
#!/usr/bin/env bash
|
|
84
|
+
set -u
|
|
85
|
+
case "${1:-}" in
|
|
86
|
+
display-message)
|
|
87
|
+
[ "${FM_FAKE_TMUX_MISSING:-0}" = 1 ] && exit 1
|
|
88
|
+
printf '%%1\n' ;;
|
|
89
|
+
capture-pane)
|
|
90
|
+
[ "${FM_FAKE_TMUX_MISSING:-0}" = 1 ] && exit 1
|
|
91
|
+
if [ "${FM_FAKE_BUSY:-0}" = 1 ]; then printf 'work in progress\n%s\n' "${FM_FAKE_BUSY_TEXT:-esc to interrupt}"
|
|
92
|
+
else printf 'all quiet\n> \n'; fi ;;
|
|
93
|
+
esac
|
|
94
|
+
exit 0
|
|
95
|
+
SH
|
|
96
|
+
cat > "$fb/herdr" <<'SH'
|
|
97
|
+
#!/usr/bin/env bash
|
|
98
|
+
set -u
|
|
99
|
+
case "${1:-}" in
|
|
100
|
+
status)
|
|
101
|
+
[ "${2:-}" = --json ] && {
|
|
102
|
+
printf '{"client":{"version":"0.7.1","protocol":14},"server":{"running":true}}\n'
|
|
103
|
+
exit 0
|
|
104
|
+
} ;;
|
|
105
|
+
server)
|
|
106
|
+
exit 0 ;;
|
|
107
|
+
pane)
|
|
108
|
+
case "${2:-}" in
|
|
109
|
+
read)
|
|
110
|
+
[ "${FM_FAKE_HERDR_MISSING:-0}" = 1 ] && exit 1
|
|
111
|
+
if [ "${FM_FAKE_HERDR_BUSY:-0}" = 1 ]; then printf 'work in progress\nesc to interrupt\n'
|
|
112
|
+
else printf 'all quiet\n> \n'; fi
|
|
113
|
+
exit 0 ;;
|
|
114
|
+
esac ;;
|
|
115
|
+
agent)
|
|
116
|
+
case "${2:-}" in
|
|
117
|
+
get)
|
|
118
|
+
[ -n "${FM_FAKE_HERDR_AGENT_STATUS:-}" ] || exit 1
|
|
119
|
+
printf '{"result":{"agent":{"agent_status":"%s"}}}\n' "$FM_FAKE_HERDR_AGENT_STATUS"
|
|
120
|
+
exit 0 ;;
|
|
121
|
+
esac ;;
|
|
122
|
+
esac
|
|
123
|
+
exit 0
|
|
124
|
+
SH
|
|
125
|
+
chmod +x "$fb/no-mistakes" "$fb/tmux" "$fb/herdr"
|
|
126
|
+
printf '%s\n' "$fb"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
make_no_timeout_toolbin() { # <dir> -> echoes toolbin path
|
|
130
|
+
local dir=$1 tb="$1/notimeoutbin" tool real
|
|
131
|
+
mkdir -p "$tb"
|
|
132
|
+
for tool in bash git grep sed head cut tail dirname perl; do
|
|
133
|
+
real=$(command -v "$tool" || true)
|
|
134
|
+
[ -n "$real" ] || fail "missing tool for no-timeout path: $tool"
|
|
135
|
+
ln -s "$real" "$tb/$tool"
|
|
136
|
+
done
|
|
137
|
+
printf '%s\n' "$tb"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Run the helper for one case dir. FM_FAKE_* env (run output, busy flag) are read
|
|
141
|
+
# from the caller's environment by the fakes above.
|
|
142
|
+
run_crew_state() { # <case-dir> <id>
|
|
143
|
+
PATH="$1/fakebin:$PATH" FM_STATE_OVERRIDE="$1/state" "$CREW_STATE" "$2"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
new_case() { # <name> -> echoes case dir with an empty state/
|
|
147
|
+
local d="$TMP_ROOT/$1"
|
|
148
|
+
mkdir -p "$d/state"
|
|
149
|
+
printf '%s\n' "$d"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
arm_idle_record() { # <state-dir> <id>
|
|
153
|
+
local state=$1 id=$2 gen
|
|
154
|
+
gen=$("$ROOT/bin/fm-busy-event.sh" arm "$state" "$id")
|
|
155
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$state" "$id" idle --gen "$gen" \
|
|
156
|
+
--source claude-hook --event stop
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# Clear the fake-driver vars and (re-)mark them exported, so the per-test plain
|
|
160
|
+
# assignments below stay exported into the fakes without an `export VAR=$(...)`
|
|
161
|
+
# command-substitution assignment (SC2155).
|
|
162
|
+
reset_fakes() {
|
|
163
|
+
FM_FAKE_AXI_STATUS=""
|
|
164
|
+
FM_FAKE_AXI_STATUS_RUN=""
|
|
165
|
+
FM_FAKE_RUNS_LIST=""
|
|
166
|
+
FM_FAKE_BUSY=0
|
|
167
|
+
FM_FAKE_BUSY_TEXT=
|
|
168
|
+
FM_FAKE_TMUX_MISSING=0
|
|
169
|
+
FM_FAKE_HERDR_BUSY=0
|
|
170
|
+
FM_FAKE_HERDR_MISSING=0
|
|
171
|
+
FM_FAKE_HERDR_AGENT_STATUS=""
|
|
172
|
+
FM_FAKE_CI_LOGS=""
|
|
173
|
+
export FM_FAKE_AXI_STATUS FM_FAKE_AXI_STATUS_RUN FM_FAKE_RUNS_LIST FM_FAKE_BUSY FM_FAKE_BUSY_TEXT FM_FAKE_TMUX_MISSING
|
|
174
|
+
export FM_FAKE_HERDR_BUSY FM_FAKE_HERDR_MISSING FM_FAKE_HERDR_AGENT_STATUS FM_FAKE_CI_LOGS
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# --- run-object fixtures (TOON, as `no-mistakes axi status` emits) -----------
|
|
178
|
+
|
|
179
|
+
run_running() { # <branch>
|
|
180
|
+
cat <<EOF
|
|
181
|
+
run:
|
|
182
|
+
id: "01RUN"
|
|
183
|
+
branch: $1
|
|
184
|
+
status: running
|
|
185
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
186
|
+
pr: ""
|
|
187
|
+
findings: none
|
|
188
|
+
steps[2]{step,status,findings,duration_ms}:
|
|
189
|
+
intent,completed,0,0
|
|
190
|
+
review,running,0,0
|
|
191
|
+
EOF
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
run_fixing() { # <branch>
|
|
195
|
+
cat <<EOF
|
|
196
|
+
run:
|
|
197
|
+
id: "01RUN"
|
|
198
|
+
branch: $1
|
|
199
|
+
status: fixing
|
|
200
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
201
|
+
pr: ""
|
|
202
|
+
findings: none
|
|
203
|
+
EOF
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
run_top_level_ci() { # <branch>
|
|
207
|
+
cat <<EOF
|
|
208
|
+
run:
|
|
209
|
+
id: "01RUN"
|
|
210
|
+
branch: $1
|
|
211
|
+
status: ci
|
|
212
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
213
|
+
pr: "https://github.com/o/r/pull/2"
|
|
214
|
+
findings: none
|
|
215
|
+
EOF
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
run_parked() { # <branch>
|
|
219
|
+
cat <<EOF
|
|
220
|
+
run:
|
|
221
|
+
id: "01RUN"
|
|
222
|
+
branch: $1
|
|
223
|
+
status: awaiting_approval
|
|
224
|
+
awaiting_agent: parked 2m10s
|
|
225
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
226
|
+
pr: ""
|
|
227
|
+
findings[2]{id,severity,file,line,action,description}:
|
|
228
|
+
r1,warning,a.go,,auto-fix,ignored error
|
|
229
|
+
r2,error,b.go,,ask-user,changes product behavior
|
|
230
|
+
gate: review
|
|
231
|
+
EOF
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
run_parked_scalar_gate_running() { # <branch>
|
|
235
|
+
cat <<EOF
|
|
236
|
+
run:
|
|
237
|
+
id: "01RUN"
|
|
238
|
+
branch: $1
|
|
239
|
+
status: running
|
|
240
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
241
|
+
pr: ""
|
|
242
|
+
findings[1]{id,severity,file,line,action,description}:
|
|
243
|
+
r1,error,b.go,,ask-user,changes product behavior
|
|
244
|
+
gate: review
|
|
245
|
+
EOF
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
run_parked_in_gate_block() { # <branch>
|
|
249
|
+
cat <<EOF
|
|
250
|
+
run:
|
|
251
|
+
id: "01RUN"
|
|
252
|
+
branch: $1
|
|
253
|
+
status: running
|
|
254
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
255
|
+
pr: ""
|
|
256
|
+
findings[1]{id,severity,file,line,action,description}:
|
|
257
|
+
r1,error,b.go,,ask-user,changes product behavior
|
|
258
|
+
gate:
|
|
259
|
+
step: review
|
|
260
|
+
status: fix_review
|
|
261
|
+
steps[3]{step,status,findings,duration_ms}:
|
|
262
|
+
intent,completed,0,0
|
|
263
|
+
review,fix_review,1,0
|
|
264
|
+
test,pending,0,0
|
|
265
|
+
EOF
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
run_passed() { # <branch>
|
|
269
|
+
cat <<EOF
|
|
270
|
+
run:
|
|
271
|
+
id: "01RUN"
|
|
272
|
+
branch: $1
|
|
273
|
+
status: completed
|
|
274
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
275
|
+
pr: "https://github.com/o/r/pull/1"
|
|
276
|
+
findings: none
|
|
277
|
+
outcome: passed
|
|
278
|
+
EOF
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
run_failed() { # <branch>
|
|
282
|
+
cat <<EOF
|
|
283
|
+
run:
|
|
284
|
+
id: "01RUN"
|
|
285
|
+
branch: $1
|
|
286
|
+
status: completed
|
|
287
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
288
|
+
pr: ""
|
|
289
|
+
findings: none
|
|
290
|
+
outcome: failed
|
|
291
|
+
EOF
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
run_ci_monitoring() { # <branch>
|
|
295
|
+
cat <<EOF
|
|
296
|
+
run:
|
|
297
|
+
id: "01RUN"
|
|
298
|
+
branch: $1
|
|
299
|
+
status: running
|
|
300
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
301
|
+
pr: "https://github.com/o/r/pull/2"
|
|
302
|
+
findings: none
|
|
303
|
+
steps[4]{step,status,findings,duration_ms}:
|
|
304
|
+
intent,completed,0,0
|
|
305
|
+
review,completed,0,0
|
|
306
|
+
push,completed,0,0
|
|
307
|
+
ci,running,0,0
|
|
308
|
+
EOF
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
run_fixing_ci_running() { # <branch>
|
|
312
|
+
cat <<EOF
|
|
313
|
+
run:
|
|
314
|
+
id: "01RUN"
|
|
315
|
+
branch: $1
|
|
316
|
+
status: fixing
|
|
317
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
318
|
+
pr: "https://github.com/o/r/pull/2"
|
|
319
|
+
findings: none
|
|
320
|
+
steps[4]{step,status,findings,duration_ms}:
|
|
321
|
+
intent,completed,0,0
|
|
322
|
+
review,completed,0,0
|
|
323
|
+
push,completed,0,0
|
|
324
|
+
ci,running,0,0
|
|
325
|
+
EOF
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
run_ci_fixing() { # <branch>
|
|
329
|
+
cat <<EOF
|
|
330
|
+
run:
|
|
331
|
+
id: "01RUN"
|
|
332
|
+
branch: $1
|
|
333
|
+
status: fixing
|
|
334
|
+
head: "${FM_FAKE_RUN_HEAD:-abc1234}"
|
|
335
|
+
pr: "https://github.com/o/r/pull/2"
|
|
336
|
+
findings: none
|
|
337
|
+
steps[4]{step,status,findings,duration_ms}:
|
|
338
|
+
intent,completed,0,0
|
|
339
|
+
review,completed,0,0
|
|
340
|
+
push,completed,0,0
|
|
341
|
+
ci,fixing,0,0
|
|
342
|
+
EOF
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
# ---------------------------------------------------------------------------
|
|
346
|
+
# (a) active run-step is authoritative
|
|
347
|
+
test_active_run_is_authoritative() {
|
|
348
|
+
reset_fakes
|
|
349
|
+
local d; d=$(new_case active)
|
|
350
|
+
make_repo_on_branch "$d/wt" fm/feat-a
|
|
351
|
+
make_fakebin "$d" >/dev/null
|
|
352
|
+
fm_write_meta "$d/state/feat-a.meta" "window=fm:fm-feat-a" "worktree=$d/wt" "kind=ship"
|
|
353
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/feat-a)"
|
|
354
|
+
local out; out=$(run_crew_state "$d" feat-a)
|
|
355
|
+
assert_contains "$out" "state: working" "active run -> working"
|
|
356
|
+
assert_contains "$out" "source: run-step" "active run -> run-step source"
|
|
357
|
+
assert_contains "$out" "validating (running)" "active run reports the step"
|
|
358
|
+
pass "active run-step is authoritative"
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
# (b) needs-decision log + a resumed (running/fixing) run = SUPERSEDED
|
|
362
|
+
test_stale_needs_decision_superseded() {
|
|
363
|
+
reset_fakes
|
|
364
|
+
local d; d=$(new_case superseded)
|
|
365
|
+
make_repo_on_branch "$d/wt" fm/feat-b
|
|
366
|
+
make_fakebin "$d" >/dev/null
|
|
367
|
+
fm_write_meta "$d/state/feat-b.meta" "window=fm:fm-feat-b" "worktree=$d/wt" "kind=ship"
|
|
368
|
+
printf 'working: started\nneeds-decision: pick A or B\n' > "$d/state/feat-b.status"
|
|
369
|
+
FM_FAKE_AXI_STATUS="$(run_fixing fm/feat-b)"
|
|
370
|
+
local out; out=$(run_crew_state "$d" feat-b)
|
|
371
|
+
assert_contains "$out" "state: working" "resumed run -> working despite needs-decision log"
|
|
372
|
+
assert_contains "$out" "source: run-step" "resumed run -> run-step source"
|
|
373
|
+
assert_contains "$out" "superseded" "stale needs-decision log flagged superseded"
|
|
374
|
+
pass "stale needs-decision over active run is superseded"
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
# blocked log + a resumed run is also superseded
|
|
378
|
+
test_stale_blocked_superseded() {
|
|
379
|
+
reset_fakes
|
|
380
|
+
local d; d=$(new_case superseded-blocked)
|
|
381
|
+
make_repo_on_branch "$d/wt" fm/feat-bb
|
|
382
|
+
make_fakebin "$d" >/dev/null
|
|
383
|
+
fm_write_meta "$d/state/feat-bb.meta" "window=fm:fm-feat-bb" "worktree=$d/wt" "kind=ship"
|
|
384
|
+
printf 'blocked: waiting on review answer\n' > "$d/state/feat-bb.status"
|
|
385
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/feat-bb)"
|
|
386
|
+
local out; out=$(run_crew_state "$d" feat-bb)
|
|
387
|
+
assert_contains "$out" "state: working" "resumed run -> working despite blocked log"
|
|
388
|
+
assert_contains "$out" "superseded" "stale blocked log flagged superseded"
|
|
389
|
+
pass "stale blocked over active run is superseded"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# (c) genuine parked run + needs-decision log AGREE -> parked, NOT superseded
|
|
393
|
+
test_genuine_parked_not_superseded() {
|
|
394
|
+
reset_fakes
|
|
395
|
+
local d; d=$(new_case parked)
|
|
396
|
+
make_repo_on_branch "$d/wt" fm/feat-c
|
|
397
|
+
make_fakebin "$d" >/dev/null
|
|
398
|
+
fm_write_meta "$d/state/feat-c.meta" "window=fm:fm-feat-c" "worktree=$d/wt" "kind=ship"
|
|
399
|
+
printf 'needs-decision: review gate\n' > "$d/state/feat-c.status"
|
|
400
|
+
FM_FAKE_AXI_STATUS="$(run_parked fm/feat-c)"
|
|
401
|
+
local out; out=$(run_crew_state "$d" feat-c)
|
|
402
|
+
assert_contains "$out" "state: parked" "genuine parked run -> parked"
|
|
403
|
+
assert_contains "$out" "source: run-step" "parked -> run-step source"
|
|
404
|
+
assert_contains "$out" "2 finding(s)" "parked includes gate finding count"
|
|
405
|
+
assert_contains "$out" "ask-user" "parked surfaces ask-user finding"
|
|
406
|
+
assert_not_contains "$out" "superseded" "agreeing parked+needs-decision not flagged stale"
|
|
407
|
+
pass "genuine parked run is not flagged superseded"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
test_scalar_gate_parked_not_superseded() {
|
|
411
|
+
reset_fakes
|
|
412
|
+
local d; d=$(new_case parked-scalar-gate)
|
|
413
|
+
make_repo_on_branch "$d/wt" fm/feat-cs
|
|
414
|
+
make_fakebin "$d" >/dev/null
|
|
415
|
+
fm_write_meta "$d/state/feat-cs.meta" "window=fm:fm-feat-cs" "worktree=$d/wt" "kind=ship"
|
|
416
|
+
printf 'needs-decision: review gate\n' > "$d/state/feat-cs.status"
|
|
417
|
+
FM_FAKE_AXI_STATUS="$(run_parked_scalar_gate_running fm/feat-cs)"
|
|
418
|
+
local out; out=$(run_crew_state "$d" feat-cs)
|
|
419
|
+
assert_contains "$out" "state: parked" "scalar gate wait -> parked"
|
|
420
|
+
assert_contains "$out" "source: run-step" "scalar gate wait -> run-step source"
|
|
421
|
+
assert_contains "$out" "parked at review" "scalar gate wait names the gate"
|
|
422
|
+
assert_contains "$out" "1 finding(s)" "scalar gate wait includes finding count"
|
|
423
|
+
assert_not_contains "$out" "superseded" "scalar gate wait not flagged stale"
|
|
424
|
+
pass "scalar gate parked run is not flagged superseded"
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
test_gate_block_parked_not_superseded() {
|
|
428
|
+
reset_fakes
|
|
429
|
+
local d; d=$(new_case parked-gate-block)
|
|
430
|
+
make_repo_on_branch "$d/wt" fm/feat-cb
|
|
431
|
+
make_fakebin "$d" >/dev/null
|
|
432
|
+
fm_write_meta "$d/state/feat-cb.meta" "window=fm:fm-feat-cb" "worktree=$d/wt" "kind=ship"
|
|
433
|
+
printf 'needs-decision: review gate\n' > "$d/state/feat-cb.status"
|
|
434
|
+
FM_FAKE_AXI_STATUS="$(run_parked_in_gate_block fm/feat-cb)"
|
|
435
|
+
local out; out=$(run_crew_state "$d" feat-cb)
|
|
436
|
+
assert_contains "$out" "state: parked" "gate block wait -> parked"
|
|
437
|
+
assert_contains "$out" "source: run-step" "gate block wait -> run-step source"
|
|
438
|
+
assert_contains "$out" "parked at review" "gate block wait names the gate"
|
|
439
|
+
assert_contains "$out" "1 finding(s)" "gate block wait includes finding count"
|
|
440
|
+
assert_not_contains "$out" "superseded" "gate block wait not flagged stale"
|
|
441
|
+
pass "gate block parked run is not flagged superseded"
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
test_ci_ready_done_log_beats_monitoring_run() {
|
|
445
|
+
reset_fakes
|
|
446
|
+
local d; d=$(new_case ci-ready)
|
|
447
|
+
make_repo_on_branch "$d/wt" fm/feat-ci
|
|
448
|
+
make_fakebin "$d" >/dev/null
|
|
449
|
+
fm_write_meta "$d/state/feat-ci.meta" "window=fm:fm-feat-ci" "worktree=$d/wt" "kind=ship"
|
|
450
|
+
printf 'done: PR https://github.com/o/r/pull/2 checks green\n' > "$d/state/feat-ci.status"
|
|
451
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-ci)"
|
|
452
|
+
local out; out=$(run_crew_state "$d" feat-ci)
|
|
453
|
+
assert_contains "$out" "state: done" "ci-ready status log -> done"
|
|
454
|
+
assert_contains "$out" "source: status-log" "ci-ready state comes from the status log"
|
|
455
|
+
assert_contains "$out" "checks green" "ci-ready detail preserves the report"
|
|
456
|
+
assert_not_contains "$out" "state: working" "ci-ready is not hidden by monitoring run"
|
|
457
|
+
pass "ci-ready status log beats monitoring run"
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# Regression for the PR #252 incident: the crew's own status log never got a
|
|
461
|
+
# "done: ... checks green" line (log_reports_ci_ready above does not apply),
|
|
462
|
+
# but the ci step's log tail shows CI is actually green and only waiting on
|
|
463
|
+
# merge/close. fm-crew-state must surface this as done, not "validating
|
|
464
|
+
# (running)", so a green PR is never silently absorbed as still-in-progress.
|
|
465
|
+
test_ci_monitoring_checks_green_surfaces_done() {
|
|
466
|
+
reset_fakes
|
|
467
|
+
local d; d=$(new_case ci-green)
|
|
468
|
+
make_repo_on_branch "$d/wt" fm/feat-cigreen
|
|
469
|
+
make_fakebin "$d" >/dev/null
|
|
470
|
+
fm_write_meta "$d/state/feat-cigreen.meta" "window=fm:fm-feat-cigreen" "worktree=$d/wt" "kind=ship"
|
|
471
|
+
# No status-log line at all: the crew never reported its own checks-green line.
|
|
472
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-cigreen)"
|
|
473
|
+
FM_FAKE_CI_LOGS=$(cat <<'EOF'
|
|
474
|
+
CI checks running, waiting for results...
|
|
475
|
+
all CI checks passed - still monitoring until merged or closed
|
|
476
|
+
EOF
|
|
477
|
+
)
|
|
478
|
+
local out; out=$(run_crew_state "$d" feat-cigreen)
|
|
479
|
+
assert_contains "$out" "state: done" "green ci-monitor run -> done"
|
|
480
|
+
assert_contains "$out" "source: run-step" "green ci-monitor -> run-step source"
|
|
481
|
+
assert_contains "$out" "checks green" "green ci-monitor detail mentions checks green"
|
|
482
|
+
assert_not_contains "$out" "state: working" "green ci-monitor must not read as still validating"
|
|
483
|
+
pass "ci-monitoring run with checks already green surfaces done"
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
test_top_level_ci_checks_green_surfaces_done() {
|
|
487
|
+
reset_fakes
|
|
488
|
+
local d; d=$(new_case top-level-ci-green)
|
|
489
|
+
make_repo_on_branch "$d/wt" fm/feat-topcigreen
|
|
490
|
+
make_fakebin "$d" >/dev/null
|
|
491
|
+
fm_write_meta "$d/state/feat-topcigreen.meta" "window=fm:fm-feat-topcigreen" "worktree=$d/wt" "kind=ship"
|
|
492
|
+
FM_FAKE_AXI_STATUS="$(run_top_level_ci fm/feat-topcigreen)"
|
|
493
|
+
FM_FAKE_CI_LOGS="all CI checks passed - still monitoring until merged or closed"
|
|
494
|
+
local out; out=$(run_crew_state "$d" feat-topcigreen)
|
|
495
|
+
assert_contains "$out" "state: done" "top-level ci with green log -> done"
|
|
496
|
+
assert_contains "$out" "source: run-step" "top-level ci green -> run-step source"
|
|
497
|
+
assert_contains "$out" "checks green" "top-level ci green detail mentions checks green"
|
|
498
|
+
assert_not_contains "$out" "state: working" "top-level ci green must not stay working"
|
|
499
|
+
pass "top-level ci status uses ci log green marker"
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
test_ci_monitoring_no_checks_terminal_surfaces_done() {
|
|
503
|
+
reset_fakes
|
|
504
|
+
local d; d=$(new_case ci-nochecks)
|
|
505
|
+
make_repo_on_branch "$d/wt" fm/feat-cinochecks
|
|
506
|
+
make_fakebin "$d" >/dev/null
|
|
507
|
+
fm_write_meta "$d/state/feat-cinochecks.meta" "window=fm:fm-feat-cinochecks" "worktree=$d/wt" "kind=ship"
|
|
508
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-cinochecks)"
|
|
509
|
+
FM_FAKE_CI_LOGS="no CI checks reported - still monitoring until merged or closed"
|
|
510
|
+
local out; out=$(run_crew_state "$d" feat-cinochecks)
|
|
511
|
+
assert_contains "$out" "state: done" "terminal no-checks ci-monitor run -> done"
|
|
512
|
+
assert_contains "$out" "checks green" "terminal no-checks ci-monitor detail mentions checks green"
|
|
513
|
+
pass "terminal no-checks ci-monitor marker surfaces done"
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
test_ci_monitoring_green_then_rearm_stays_working() {
|
|
517
|
+
reset_fakes
|
|
518
|
+
local d; d=$(new_case ci-green-then-rearm)
|
|
519
|
+
make_repo_on_branch "$d/wt" fm/feat-cirearm
|
|
520
|
+
make_fakebin "$d" >/dev/null
|
|
521
|
+
fm_write_meta "$d/state/feat-cirearm.meta" "window=fm:fm-feat-cirearm" "worktree=$d/wt" "kind=ship"
|
|
522
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-cirearm)"
|
|
523
|
+
FM_FAKE_CI_LOGS=$(cat <<'EOF'
|
|
524
|
+
all CI checks passed - still monitoring until merged or closed
|
|
525
|
+
base branch advanced (aaaaaaa..bbbbbbb), re-arming CI monitor timeout
|
|
526
|
+
EOF
|
|
527
|
+
)
|
|
528
|
+
local out; out=$(run_crew_state "$d" feat-cirearm)
|
|
529
|
+
assert_contains "$out" "state: working" "base-advance rearm marker -> working"
|
|
530
|
+
assert_not_contains "$out" "state: done" "base-advance rearm marker must not read as done"
|
|
531
|
+
assert_not_contains "$out" "checks green" "base-advance rearm marker must not read as checks green"
|
|
532
|
+
pass "base-advance rearm after green stays working"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
test_ci_monitoring_no_checks_yet_stays_working() {
|
|
536
|
+
reset_fakes
|
|
537
|
+
local d; d=$(new_case ci-nochecks-yet)
|
|
538
|
+
make_repo_on_branch "$d/wt" fm/feat-cinochecksyet
|
|
539
|
+
make_fakebin "$d" >/dev/null
|
|
540
|
+
fm_write_meta "$d/state/feat-cinochecksyet.meta" "window=fm:fm-feat-cinochecksyet" "worktree=$d/wt" "kind=ship"
|
|
541
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-cinochecksyet)"
|
|
542
|
+
FM_FAKE_CI_LOGS=$(cat <<'EOF'
|
|
543
|
+
no CI checks reported - still monitoring until merged or closed
|
|
544
|
+
base branch advanced (aaaaaaa..bbbbbbb), re-arming CI monitor timeout
|
|
545
|
+
no CI checks reported yet, waiting for checks to register...
|
|
546
|
+
EOF
|
|
547
|
+
)
|
|
548
|
+
local out; out=$(run_crew_state "$d" feat-cinochecksyet)
|
|
549
|
+
assert_contains "$out" "state: working" "pending no-checks marker -> working"
|
|
550
|
+
assert_not_contains "$out" "state: done" "pending no-checks marker must not read as done"
|
|
551
|
+
assert_not_contains "$out" "checks green" "pending no-checks marker must not read as checks green"
|
|
552
|
+
pass "pending no-checks ci-monitor marker stays working"
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
test_ci_monitoring_still_waiting_stays_working() {
|
|
556
|
+
reset_fakes
|
|
557
|
+
local d; d=$(new_case ci-waiting)
|
|
558
|
+
make_repo_on_branch "$d/wt" fm/feat-ciwait
|
|
559
|
+
make_fakebin "$d" >/dev/null
|
|
560
|
+
fm_write_meta "$d/state/feat-ciwait.meta" "window=fm:fm-feat-ciwait" "worktree=$d/wt" "kind=ship"
|
|
561
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-ciwait)"
|
|
562
|
+
FM_FAKE_CI_LOGS="CI checks running, waiting for results..."
|
|
563
|
+
local out; out=$(run_crew_state "$d" feat-ciwait)
|
|
564
|
+
assert_contains "$out" "state: working" "ci step still red -> working"
|
|
565
|
+
assert_not_contains "$out" "checks green" "no green marker present -> no checks-green detail"
|
|
566
|
+
pass "ci-monitoring run with checks not yet green stays working"
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
# A later merge-conflict auto-fix round after an earlier green reading must
|
|
570
|
+
# not be masked: the MOST RECENT marker in the log tail wins.
|
|
571
|
+
test_ci_monitoring_green_then_new_issue_stays_working() {
|
|
572
|
+
reset_fakes
|
|
573
|
+
local d; d=$(new_case ci-green-then-issue)
|
|
574
|
+
make_repo_on_branch "$d/wt" fm/feat-cirelapse
|
|
575
|
+
make_fakebin "$d" >/dev/null
|
|
576
|
+
fm_write_meta "$d/state/feat-cirelapse.meta" "window=fm:fm-feat-cirelapse" "worktree=$d/wt" "kind=ship"
|
|
577
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-cirelapse)"
|
|
578
|
+
FM_FAKE_CI_LOGS=$(cat <<'EOF'
|
|
579
|
+
all CI checks passed - still monitoring until merged or closed
|
|
580
|
+
base branch advanced (aaaaaaa..bbbbbbb), re-arming CI monitor timeout
|
|
581
|
+
issues detected: merge conflict - auto-fixing (attempt 2/10)...
|
|
582
|
+
EOF
|
|
583
|
+
)
|
|
584
|
+
local out; out=$(run_crew_state "$d" feat-cirelapse)
|
|
585
|
+
assert_contains "$out" "state: working" "a later relapse marker must win over an earlier green one"
|
|
586
|
+
assert_not_contains "$out" "state: done" "relapsed ci run must not read as done"
|
|
587
|
+
pass "a fresh issue after an earlier green reading is not masked"
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
test_ci_ready_done_log_relapse_stays_working() {
|
|
591
|
+
reset_fakes
|
|
592
|
+
local d; d=$(new_case ci-ready-then-relapse)
|
|
593
|
+
make_repo_on_branch "$d/wt" fm/feat-cireadyrelapse
|
|
594
|
+
make_fakebin "$d" >/dev/null
|
|
595
|
+
fm_write_meta "$d/state/feat-cireadyrelapse.meta" "window=fm:fm-feat-cireadyrelapse" "worktree=$d/wt" "kind=ship"
|
|
596
|
+
printf 'done: PR https://github.com/o/r/pull/2 checks green\n' > "$d/state/feat-cireadyrelapse.status"
|
|
597
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/feat-cireadyrelapse)"
|
|
598
|
+
FM_FAKE_CI_LOGS=$(cat <<'EOF'
|
|
599
|
+
all CI checks passed - still monitoring until merged or closed
|
|
600
|
+
base branch advanced (aaaaaaa..bbbbbbb), re-arming CI monitor timeout
|
|
601
|
+
CI checks running, waiting for results...
|
|
602
|
+
EOF
|
|
603
|
+
)
|
|
604
|
+
local out; out=$(run_crew_state "$d" feat-cireadyrelapse)
|
|
605
|
+
assert_contains "$out" "state: working" "a stale ready status must not mask a later CI relapse"
|
|
606
|
+
assert_contains "$out" "source: run-step" "relapsed ci run remains run-step sourced"
|
|
607
|
+
assert_not_contains "$out" "state: done" "relapsed ci run with stale done log must not read as done"
|
|
608
|
+
pass "stale checks-green status log does not mask CI relapse"
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
test_ci_fixing_after_green_stays_working() {
|
|
612
|
+
reset_fakes
|
|
613
|
+
local d; d=$(new_case ci-fixing-after-green)
|
|
614
|
+
make_repo_on_branch "$d/wt" fm/feat-cifixing
|
|
615
|
+
make_fakebin "$d" >/dev/null
|
|
616
|
+
fm_write_meta "$d/state/feat-cifixing.meta" "window=fm:fm-feat-cifixing" "worktree=$d/wt" "kind=ship"
|
|
617
|
+
printf 'done: PR https://github.com/o/r/pull/2 checks green\n' > "$d/state/feat-cifixing.status"
|
|
618
|
+
FM_FAKE_AXI_STATUS="$(run_ci_fixing fm/feat-cifixing)"
|
|
619
|
+
FM_FAKE_CI_LOGS="all CI checks passed - still monitoring until merged or closed"
|
|
620
|
+
local out; out=$(run_crew_state "$d" feat-cifixing)
|
|
621
|
+
assert_contains "$out" "state: working" "ci fixing step must stay working"
|
|
622
|
+
assert_contains "$out" "source: run-step" "ci fixing remains run-step sourced"
|
|
623
|
+
assert_not_contains "$out" "state: done" "ci fixing must not read as checks-green done"
|
|
624
|
+
pass "ci fixing is not overridden by an earlier green marker"
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
test_top_level_fixing_ci_running_after_green_stays_working() {
|
|
628
|
+
reset_fakes
|
|
629
|
+
local d; d=$(new_case top-level-fixing-ci-running)
|
|
630
|
+
make_repo_on_branch "$d/wt" fm/feat-topfixingci
|
|
631
|
+
make_fakebin "$d" >/dev/null
|
|
632
|
+
fm_write_meta "$d/state/feat-topfixingci.meta" "window=fm:fm-feat-topfixingci" "worktree=$d/wt" "kind=ship"
|
|
633
|
+
FM_FAKE_AXI_STATUS="$(run_fixing_ci_running fm/feat-topfixingci)"
|
|
634
|
+
FM_FAKE_CI_LOGS="all CI checks passed - still monitoring until merged or closed"
|
|
635
|
+
local out; out=$(run_crew_state "$d" feat-topfixingci)
|
|
636
|
+
assert_contains "$out" "state: working" "top-level fixing with ci running must stay working"
|
|
637
|
+
assert_contains "$out" "source: run-step" "top-level fixing with ci running remains run-step sourced"
|
|
638
|
+
assert_contains "$out" "validating (fixing)" "top-level fixing keeps fixing detail"
|
|
639
|
+
assert_not_contains "$out" "state: done" "top-level fixing must not use stale green marker"
|
|
640
|
+
pass "top-level fixing is not overridden by a stale ci running row"
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
test_top_level_fixing_done_log_stays_working() {
|
|
644
|
+
reset_fakes
|
|
645
|
+
local d; d=$(new_case top-level-fixing-done-log)
|
|
646
|
+
make_repo_on_branch "$d/wt" fm/feat-topfixing
|
|
647
|
+
make_fakebin "$d" >/dev/null
|
|
648
|
+
fm_write_meta "$d/state/feat-topfixing.meta" "window=fm:fm-feat-topfixing" "worktree=$d/wt" "kind=ship"
|
|
649
|
+
printf 'done: PR https://github.com/o/r/pull/2 checks green\n' > "$d/state/feat-topfixing.status"
|
|
650
|
+
FM_FAKE_AXI_STATUS="$(run_fixing fm/feat-topfixing)"
|
|
651
|
+
FM_FAKE_CI_LOGS="all CI checks passed - still monitoring until merged or closed"
|
|
652
|
+
local out; out=$(run_crew_state "$d" feat-topfixing)
|
|
653
|
+
assert_contains "$out" "state: working" "top-level fixing must stay working"
|
|
654
|
+
assert_contains "$out" "source: run-step" "top-level fixing remains run-step sourced"
|
|
655
|
+
assert_contains "$out" "validating (fixing)" "top-level fixing keeps fixing detail"
|
|
656
|
+
assert_not_contains "$out" "state: done" "top-level fixing must not read as stale checks-green done"
|
|
657
|
+
pass "top-level fixing is not overridden by a stale done log"
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
# (d) terminal run-step is authoritative
|
|
661
|
+
test_terminal_passed() {
|
|
662
|
+
reset_fakes
|
|
663
|
+
local d; d=$(new_case passed)
|
|
664
|
+
make_repo_on_branch "$d/wt" fm/feat-d
|
|
665
|
+
make_fakebin "$d" >/dev/null
|
|
666
|
+
fm_write_meta "$d/state/feat-d.meta" "window=fm:fm-feat-d" "worktree=$d/wt" "kind=ship"
|
|
667
|
+
FM_FAKE_AXI_STATUS="$(run_passed fm/feat-d)"
|
|
668
|
+
local out; out=$(run_crew_state "$d" feat-d)
|
|
669
|
+
assert_contains "$out" "state: done" "passed run -> done"
|
|
670
|
+
assert_contains "$out" "source: run-step" "passed -> run-step source"
|
|
671
|
+
pass "terminal passed run is authoritative"
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
test_terminal_failed() {
|
|
675
|
+
reset_fakes
|
|
676
|
+
local d; d=$(new_case failed)
|
|
677
|
+
make_repo_on_branch "$d/wt" fm/feat-e
|
|
678
|
+
make_fakebin "$d" >/dev/null
|
|
679
|
+
fm_write_meta "$d/state/feat-e.meta" "window=fm:fm-feat-e" "worktree=$d/wt" "kind=ship"
|
|
680
|
+
FM_FAKE_AXI_STATUS="$(run_failed fm/feat-e)"
|
|
681
|
+
local out; out=$(run_crew_state "$d" feat-e)
|
|
682
|
+
assert_contains "$out" "state: failed" "failed run -> failed"
|
|
683
|
+
assert_contains "$out" "source: run-step" "failed -> run-step source"
|
|
684
|
+
pass "terminal failed run is authoritative"
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
# (e) cross-branch attribution: `axi status` returns ANOTHER branch's run (the
|
|
688
|
+
# routine case once more than one crew validates the same underlying repo
|
|
689
|
+
# concurrently - they share ONE no-mistakes repo registration), so the helper
|
|
690
|
+
# falls back to the real top-level `no-mistakes runs` listing to learn whether
|
|
691
|
+
# THIS branch has an active run of its own. Regression coverage for the
|
|
692
|
+
# 2026-07-02 herdr incident: the old fallback shelled out to `no-mistakes axi`
|
|
693
|
+
# (bare) expecting a `runs[N]{...}:` TOON table that the real CLI never emits
|
|
694
|
+
# (verified against the installed v1.32.2 - the `axi` surface has no
|
|
695
|
+
# runs-listing subcommand at all), so attribution silently failed every time
|
|
696
|
+
# the repo-wide answer was not this crew's own branch.
|
|
697
|
+
test_cross_branch_attribution_via_runs_list() {
|
|
698
|
+
reset_fakes
|
|
699
|
+
local d short; d=$(new_case crossbranch)
|
|
700
|
+
make_repo_on_branch "$d/wt" fm/feat-f
|
|
701
|
+
short=$(git -C "$d/wt" rev-parse --short=7 HEAD)
|
|
702
|
+
make_fakebin "$d" >/dev/null
|
|
703
|
+
fm_write_meta "$d/state/feat-f.meta" "window=fm:fm-feat-f" "worktree=$d/wt" "kind=ship"
|
|
704
|
+
# The repo-wide active/most-recent run belongs to a different crew's branch.
|
|
705
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/other-crew)"
|
|
706
|
+
# Real `no-mistakes runs` shape: plain text, newest-first, no run id, no
|
|
707
|
+
# quoting - "<status> <branch> <short-sha> <date> [<pr-url>]".
|
|
708
|
+
FM_FAKE_RUNS_LIST="$(cat <<EOF
|
|
709
|
+
running fm/other-crew aaaaaaa 2026-07-02 22:10
|
|
710
|
+
running fm/feat-f ${short} 2026-07-02 22:05
|
|
711
|
+
EOF
|
|
712
|
+
)"
|
|
713
|
+
local out; out=$(run_crew_state "$d" feat-f)
|
|
714
|
+
assert_contains "$out" "state: working" "this branch's own run attributed via the runs list"
|
|
715
|
+
assert_contains "$out" "source: run-step" "runs-list-resolved run -> run-step source"
|
|
716
|
+
pass "cross-branch run is attributed via the real runs list"
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
# The runs list is newest-first; a branch with an OLDER completed run must not
|
|
720
|
+
# shadow its own newer active one - the first (topmost) matching row wins.
|
|
721
|
+
test_cross_branch_attribution_picks_most_recent_row() {
|
|
722
|
+
reset_fakes
|
|
723
|
+
local d short; d=$(new_case crossbranch-mostrecent)
|
|
724
|
+
make_repo_on_branch "$d/wt" fm/feat-fq
|
|
725
|
+
short=$(git -C "$d/wt" rev-parse --short=7 HEAD)
|
|
726
|
+
make_fakebin "$d" >/dev/null
|
|
727
|
+
fm_write_meta "$d/state/feat-fq.meta" "window=fm:fm-feat-fq" "worktree=$d/wt" "kind=ship"
|
|
728
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/other-crew)"
|
|
729
|
+
FM_FAKE_RUNS_LIST="$(cat <<EOF
|
|
730
|
+
running fm/other-crew aaaaaaa 2026-07-02 22:10
|
|
731
|
+
running fm/feat-fq ${short} 2026-07-02 21:50
|
|
732
|
+
completed fm/feat-fq bbbbbbb 2026-07-02 20:00 https://github.com/o/r/pull/1
|
|
733
|
+
EOF
|
|
734
|
+
)"
|
|
735
|
+
local out; out=$(run_crew_state "$d" feat-fq)
|
|
736
|
+
assert_contains "$out" "state: working" "most recent (running) row wins over an older completed row"
|
|
737
|
+
assert_contains "$out" "source: run-step" "most-recent-row resolution -> run-step source"
|
|
738
|
+
pass "cross-branch attribution picks the branch's most recent row"
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
test_coarse_run_does_not_probe_other_branch_ci_log_for_ready_status() {
|
|
742
|
+
reset_fakes
|
|
743
|
+
local d short; d=$(new_case coarse-ready-other-log)
|
|
744
|
+
make_repo_on_branch "$d/wt" fm/feat-coarseready
|
|
745
|
+
short=$(git -C "$d/wt" rev-parse --short=7 HEAD)
|
|
746
|
+
make_fakebin "$d" >/dev/null
|
|
747
|
+
fm_write_meta "$d/state/feat-coarseready.meta" "window=fm:fm-feat-coarseready" "worktree=$d/wt" "kind=ship"
|
|
748
|
+
printf 'done: PR https://github.com/o/r/pull/4 checks green\n' > "$d/state/feat-coarseready.status"
|
|
749
|
+
FM_FAKE_AXI_STATUS="$(run_ci_monitoring fm/other-crew)"
|
|
750
|
+
FM_FAKE_RUNS_LIST="$(cat <<EOF
|
|
751
|
+
running fm/other-crew aaaaaaa 2026-07-02 22:10
|
|
752
|
+
running fm/feat-coarseready ${short} 2026-07-02 22:05
|
|
753
|
+
EOF
|
|
754
|
+
)"
|
|
755
|
+
FM_FAKE_CI_LOGS="CI checks running, waiting for results..."
|
|
756
|
+
local out; out=$(run_crew_state "$d" feat-coarseready)
|
|
757
|
+
assert_contains "$out" "state: done" "coarse ready status -> done"
|
|
758
|
+
assert_contains "$out" "source: status-log" "coarse ready status remains status-log sourced"
|
|
759
|
+
assert_not_contains "$out" "state: working" "coarse ready status must not be suppressed by another branch log"
|
|
760
|
+
pass "coarse run does not probe another branch's ci log"
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
# A different-branch run with NO matching runs-list row must NOT be
|
|
764
|
+
# misattributed, and must not be treated as a false "working" verdict either.
|
|
765
|
+
test_other_branch_run_ignored() {
|
|
766
|
+
reset_fakes
|
|
767
|
+
local d; d=$(new_case otherbranch)
|
|
768
|
+
make_repo_on_branch "$d/wt" fm/feat-g
|
|
769
|
+
make_fakebin "$d" >/dev/null
|
|
770
|
+
fm_write_meta "$d/state/feat-g.meta" "window=fm:fm-feat-g" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
771
|
+
printf 'done: implemented, ready to validate\n' > "$d/state/feat-g.status"
|
|
772
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/some-other)"
|
|
773
|
+
FM_FAKE_RUNS_LIST="$(cat <<'EOF'
|
|
774
|
+
running fm/some-other aaaaaaa 2026-07-02 22:10
|
|
775
|
+
EOF
|
|
776
|
+
)"
|
|
777
|
+
FM_FAKE_BUSY=0
|
|
778
|
+
arm_idle_record "$d/state" feat-g
|
|
779
|
+
local out; out=$(run_crew_state "$d" feat-g)
|
|
780
|
+
assert_not_contains "$out" "source: run-step" "another branch's run not misattributed"
|
|
781
|
+
assert_contains "$out" "source: status-log" "no own run -> falls back to status-log"
|
|
782
|
+
assert_contains "$out" "state: done" "falls back to the log verb"
|
|
783
|
+
pass "another branch's run is ignored, falls back"
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
# (f) no run for this crew + a busy pane -> working via pane
|
|
787
|
+
test_no_run_busy_pane() {
|
|
788
|
+
reset_fakes
|
|
789
|
+
local d; d=$(new_case busy)
|
|
790
|
+
make_repo_on_branch "$d/wt" fm/feat-h
|
|
791
|
+
make_fakebin "$d" >/dev/null
|
|
792
|
+
fm_write_meta "$d/state/feat-h.meta" "window=fm:fm-feat-h" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
793
|
+
# No matching run anywhere. The busy verdict comes from the crew's own
|
|
794
|
+
# semantic lifecycle record (bin/fm-busy-lib.sh), not from rendered text.
|
|
795
|
+
FM_FAKE_AXI_STATUS=""
|
|
796
|
+
FM_FAKE_RUNS_LIST=""
|
|
797
|
+
FM_FAKE_BUSY=1
|
|
798
|
+
local gen; gen=$("$ROOT/bin/fm-busy-event.sh" arm "$d/state" feat-h)
|
|
799
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$d/state" feat-h busy --gen "$gen" \
|
|
800
|
+
--source claude-hook --event user-prompt-submit
|
|
801
|
+
local out; out=$(run_crew_state "$d" feat-h)
|
|
802
|
+
assert_contains "$out" "state: working" "busy record -> working"
|
|
803
|
+
assert_contains "$out" "source: pane" "busy record -> pane source"
|
|
804
|
+
assert_contains "$out" "claude-hook" "the working verdict names its semantic source"
|
|
805
|
+
pass "no run + a busy semantic record reads working, attributed to its source"
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
# A converted adapter must NOT read working from rendered footer text: the
|
|
809
|
+
# redesign removed that dependency, so a pane painting "esc to interrupt" with
|
|
810
|
+
# no semantic record is unknown, never working and never silently idle.
|
|
811
|
+
test_no_run_footer_text_alone_is_not_working() {
|
|
812
|
+
reset_fakes
|
|
813
|
+
local d; d=$(new_case busy-footer-only)
|
|
814
|
+
make_repo_on_branch "$d/wt" fm/feat-h2
|
|
815
|
+
make_fakebin "$d" >/dev/null
|
|
816
|
+
fm_write_meta "$d/state/feat-h2.meta" "window=fm:fm-feat-h2" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
817
|
+
FM_FAKE_AXI_STATUS=""
|
|
818
|
+
FM_FAKE_RUNS_LIST=""
|
|
819
|
+
FM_FAKE_BUSY=1
|
|
820
|
+
printf 'done: stale completion event\n' > "$d/state/feat-h2.status"
|
|
821
|
+
local out; out=$(run_crew_state "$d" feat-h2)
|
|
822
|
+
assert_not_contains "$out" "state: working" "a footer alone must not read working for a converted adapter"
|
|
823
|
+
assert_contains "$out" "state: unknown" "no semantic record -> unknown"
|
|
824
|
+
assert_not_contains "$out" "source: status-log" "unknown semantic state must not fall through to a stale log"
|
|
825
|
+
pass "a converted adapter never reads working from rendered footer text"
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
# Grok keeps its isolated temporary rendered-tail fallback until its structured
|
|
829
|
+
# lifecycle is live-verified, so a grok crew still reads working from its own
|
|
830
|
+
# verified signature.
|
|
831
|
+
test_no_run_grok_uses_isolated_fallback() {
|
|
832
|
+
reset_fakes
|
|
833
|
+
local d; d=$(new_case busy-grok)
|
|
834
|
+
make_repo_on_branch "$d/wt" fm/feat-h3
|
|
835
|
+
make_fakebin "$d" >/dev/null
|
|
836
|
+
fm_write_meta "$d/state/feat-h3.meta" "window=fm:fm-feat-h3" "worktree=$d/wt" "kind=ship" "harness=grok"
|
|
837
|
+
FM_FAKE_AXI_STATUS=""
|
|
838
|
+
FM_FAKE_RUNS_LIST=""
|
|
839
|
+
FM_FAKE_BUSY=1
|
|
840
|
+
FM_FAKE_BUSY_TEXT='Ctrl+c:cancel'
|
|
841
|
+
export FM_FAKE_BUSY_TEXT
|
|
842
|
+
local out; out=$(run_crew_state "$d" feat-h3)
|
|
843
|
+
assert_contains "$out" "state: working" "grok busy tail -> working"
|
|
844
|
+
assert_contains "$out" "grok-regex" "the grok verdict names its isolated fallback source"
|
|
845
|
+
pass "grok still reads working through its isolated rendered-tail fallback"
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
test_no_run_herdr_unknown_uses_backend_capture() {
|
|
849
|
+
command -v jq >/dev/null 2>&1 || { pass "herdr pane fallback skipped without jq"; return; }
|
|
850
|
+
reset_fakes
|
|
851
|
+
local d; d=$(new_case herdr-busy)
|
|
852
|
+
make_repo_on_branch "$d/wt" fm/feat-herdr
|
|
853
|
+
make_fakebin "$d" >/dev/null
|
|
854
|
+
fm_write_meta "$d/state/feat-herdr.meta" "window=default:w1:p2" "worktree=$d/wt" "kind=ship" \
|
|
855
|
+
"backend=herdr" "harness=claude"
|
|
856
|
+
FM_FAKE_AXI_STATUS=""
|
|
857
|
+
FM_FAKE_RUNS_LIST=""
|
|
858
|
+
FM_FAKE_TMUX_MISSING=1
|
|
859
|
+
FM_FAKE_HERDR_BUSY=1
|
|
860
|
+
FM_FAKE_HERDR_AGENT_STATUS=working
|
|
861
|
+
local out; out=$(run_crew_state "$d" feat-herdr)
|
|
862
|
+
assert_contains "$out" "state: working" "herdr native busy -> working"
|
|
863
|
+
assert_contains "$out" "source: pane" "herdr native busy -> pane source"
|
|
864
|
+
assert_contains "$out" "herdr-native" "the herdr verdict names its native source"
|
|
865
|
+
pass "herdr's native busy verdict reads working with no record present"
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
# Regression (2026-07 herdr false-surface incident, now solved semantically):
|
|
869
|
+
# herdr's agent.get reports generation state ("working" only while the model is
|
|
870
|
+
# actively streaming - docs/herdr-backend.md "Busy state"), not "this crew's
|
|
871
|
+
# turn is still in progress". A crew blocked on its own long-running foreground
|
|
872
|
+
# `no-mistakes axi run` (no --yes; blocks until a gate or outcome) is not
|
|
873
|
+
# generating for that whole span, so agent.get reads idle. The crew's own
|
|
874
|
+
# semantic lifecycle record still says busy for the whole turn, and it outranks
|
|
875
|
+
# the narrower native verdict - so the crew is no longer misread as not-working.
|
|
876
|
+
test_no_run_herdr_idle_agent_status_outranked_by_record() {
|
|
877
|
+
command -v jq >/dev/null 2>&1 || { pass "herdr idle corroboration skipped without jq"; return; }
|
|
878
|
+
reset_fakes
|
|
879
|
+
local d; d=$(new_case herdr-idle-busy-record)
|
|
880
|
+
make_repo_on_branch "$d/wt" fm/feat-herdr-idle
|
|
881
|
+
make_fakebin "$d" >/dev/null
|
|
882
|
+
fm_write_meta "$d/state/feat-herdr-idle.meta" "window=default:w1:p3" "worktree=$d/wt" "kind=ship" \
|
|
883
|
+
"backend=herdr" "harness=claude"
|
|
884
|
+
# No run attributable (mirrors a no-mistakes run-step lookup that found no
|
|
885
|
+
# matching row within the configured runs-list window): the crew's semantic
|
|
886
|
+
# busy state is the only remaining signal.
|
|
887
|
+
FM_FAKE_AXI_STATUS=""
|
|
888
|
+
FM_FAKE_RUNS_LIST=""
|
|
889
|
+
FM_FAKE_TMUX_MISSING=1
|
|
890
|
+
FM_FAKE_HERDR_AGENT_STATUS=idle
|
|
891
|
+
FM_FAKE_HERDR_BUSY=0
|
|
892
|
+
local gen; gen=$("$ROOT/bin/fm-busy-event.sh" arm "$d/state" feat-herdr-idle)
|
|
893
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$d/state" feat-herdr-idle busy --gen "$gen" \
|
|
894
|
+
--source claude-hook --event user-prompt-submit
|
|
895
|
+
local out; out=$(run_crew_state "$d" feat-herdr-idle)
|
|
896
|
+
assert_contains "$out" "state: working" "a busy record with herdr idle agent_status -> working"
|
|
897
|
+
assert_contains "$out" "claude-hook" "the record's source outranks herdr's narrower native verdict"
|
|
898
|
+
pass "a mid-tool-call crew stays working because its record outranks herdr's generation state"
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
# The record must not mask a genuinely idle or human-blocked agent: an idle
|
|
902
|
+
# record with idle agent_status still reads not-busy.
|
|
903
|
+
test_no_run_herdr_idle_agent_status_and_idle_record_stays_idle() {
|
|
904
|
+
command -v jq >/dev/null 2>&1 || { pass "herdr idle+idle-record skipped without jq"; return; }
|
|
905
|
+
reset_fakes
|
|
906
|
+
local d; d=$(new_case herdr-idle-idle-record)
|
|
907
|
+
make_repo_on_branch "$d/wt" fm/feat-herdr-stopped
|
|
908
|
+
make_fakebin "$d" >/dev/null
|
|
909
|
+
fm_write_meta "$d/state/feat-herdr-stopped.meta" "window=default:w1:p4" "worktree=$d/wt" "kind=ship" \
|
|
910
|
+
"backend=herdr" "harness=claude"
|
|
911
|
+
printf 'working: implementing\n' > "$d/state/feat-herdr-stopped.status"
|
|
912
|
+
FM_FAKE_AXI_STATUS=""
|
|
913
|
+
FM_FAKE_RUNS_LIST=""
|
|
914
|
+
FM_FAKE_TMUX_MISSING=1
|
|
915
|
+
FM_FAKE_HERDR_AGENT_STATUS=idle
|
|
916
|
+
FM_FAKE_HERDR_BUSY=0
|
|
917
|
+
local gen; gen=$("$ROOT/bin/fm-busy-event.sh" arm "$d/state" feat-herdr-stopped)
|
|
918
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$d/state" feat-herdr-stopped idle --gen "$gen" \
|
|
919
|
+
--source claude-hook --event stop
|
|
920
|
+
local out; out=$(run_crew_state "$d" feat-herdr-stopped)
|
|
921
|
+
assert_not_contains "$out" "source: pane" "an idle record must not read as busy"
|
|
922
|
+
assert_contains "$out" "source: status-log" "an idle record falls to the status log"
|
|
923
|
+
pass "an idle record with idle agent_status stays not-busy (no regression for a human-blocked agent)"
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
# (g) no run + idle pane -> the status-log verb, as-is
|
|
927
|
+
test_no_run_idle_pane_uses_log() {
|
|
928
|
+
reset_fakes
|
|
929
|
+
local d; d=$(new_case idle)
|
|
930
|
+
make_repo_on_branch "$d/wt" fm/feat-i
|
|
931
|
+
make_fakebin "$d" >/dev/null
|
|
932
|
+
fm_write_meta "$d/state/feat-i.meta" "window=fm:fm-feat-i" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
933
|
+
printf 'needs-decision: which database?\n' > "$d/state/feat-i.status"
|
|
934
|
+
FM_FAKE_AXI_STATUS=""
|
|
935
|
+
FM_FAKE_BUSY=0
|
|
936
|
+
arm_idle_record "$d/state" feat-i
|
|
937
|
+
local out; out=$(run_crew_state "$d" feat-i)
|
|
938
|
+
assert_contains "$out" "state: parked" "needs-decision log -> parked"
|
|
939
|
+
assert_contains "$out" "source: status-log" "idle pane -> status-log source"
|
|
940
|
+
pass "no run + idle pane uses the status-log verb"
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
test_no_run_idle_pane_uses_keyed_log() {
|
|
944
|
+
reset_fakes
|
|
945
|
+
local d; d=$(new_case keyed-idle)
|
|
946
|
+
make_repo_on_branch "$d/wt" fm/feat-keyed
|
|
947
|
+
make_fakebin "$d" >/dev/null
|
|
948
|
+
fm_write_meta "$d/state/feat-keyed.meta" "window=fm:fm-feat-keyed" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
949
|
+
printf 'needs-decision [key=q1]: which database?\n' > "$d/state/feat-keyed.status"
|
|
950
|
+
FM_FAKE_AXI_STATUS=""
|
|
951
|
+
FM_FAKE_BUSY=0
|
|
952
|
+
arm_idle_record "$d/state" feat-keyed
|
|
953
|
+
local out; out=$(run_crew_state "$d" feat-keyed)
|
|
954
|
+
assert_contains "$out" "state: parked" "keyed needs-decision log -> parked"
|
|
955
|
+
assert_contains "$out" "which database?" "key token is excluded from status detail"
|
|
956
|
+
pass "no run + idle pane parses keyed status syntax"
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
# (g') no run + idle pane on a DECLARED external-wait pause -> state: paused, so a
|
|
960
|
+
# supervisor reading the crew sees a distinct pause (and its reason) rather than a
|
|
961
|
+
# wedge-suspect idle. This is the reader half the watcher/daemon build on.
|
|
962
|
+
test_no_run_idle_pane_paused() {
|
|
963
|
+
reset_fakes
|
|
964
|
+
local d; d=$(new_case paused)
|
|
965
|
+
make_repo_on_branch "$d/wt" fm/feat-pause
|
|
966
|
+
make_fakebin "$d" >/dev/null
|
|
967
|
+
fm_write_meta "$d/state/feat-pause.meta" "window=fm:fm-feat-pause" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
968
|
+
printf 'paused: holding for the upstream tool release\n' > "$d/state/feat-pause.status"
|
|
969
|
+
FM_FAKE_AXI_STATUS=""
|
|
970
|
+
FM_FAKE_BUSY=0
|
|
971
|
+
arm_idle_record "$d/state" feat-pause
|
|
972
|
+
local out; out=$(run_crew_state "$d" feat-pause)
|
|
973
|
+
assert_contains "$out" "state: paused" "paused log -> paused"
|
|
974
|
+
assert_contains "$out" "source: status-log" "idle pause -> status-log source"
|
|
975
|
+
assert_contains "$out" "holding for the upstream tool release" "the pause reason is carried in the detail"
|
|
976
|
+
pass "no run + idle pane on a paused: status reports state: paused with its reason"
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
test_no_run_idle_pane_custom_paused_verb() {
|
|
980
|
+
reset_fakes
|
|
981
|
+
local d; d=$(new_case custom-paused)
|
|
982
|
+
make_repo_on_branch "$d/wt" fm/feat-custom-pause
|
|
983
|
+
make_fakebin "$d" >/dev/null
|
|
984
|
+
fm_write_meta "$d/state/feat-custom-pause.meta" "window=fm:fm-feat-custom-pause" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
985
|
+
printf 'awaiting: vendor maintenance window\n' > "$d/state/feat-custom-pause.status"
|
|
986
|
+
FM_FAKE_AXI_STATUS=""
|
|
987
|
+
FM_FAKE_BUSY=0
|
|
988
|
+
arm_idle_record "$d/state" feat-custom-pause
|
|
989
|
+
local out; out=$(FM_CLASSIFY_PAUSED_VERB=awaiting run_crew_state "$d" feat-custom-pause)
|
|
990
|
+
assert_contains "$out" "state: paused" "custom paused verb -> paused"
|
|
991
|
+
assert_contains "$out" "source: status-log" "custom paused verb -> status-log source"
|
|
992
|
+
assert_contains "$out" "vendor maintenance window" "custom pause preserves its reason"
|
|
993
|
+
printf 'paused: default verb no longer selected\n' > "$d/state/feat-custom-pause.status"
|
|
994
|
+
out=$(FM_CLASSIFY_PAUSED_VERB=awaiting run_crew_state "$d" feat-custom-pause)
|
|
995
|
+
assert_contains "$out" "state: unknown" "custom paused verb replaces the default"
|
|
996
|
+
pass "no run + idle pane honors the configured paused verb"
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
# A trailing keyed resolved: event is a decision-CLOSING event, not a run-state
|
|
1000
|
+
# verb. It must never become the current state or leak its resolution prose as the
|
|
1001
|
+
# detail: a healthy idle secondmate that just closed a keyed decision falls through
|
|
1002
|
+
# to the idle default (unknown/none), not `unknown` with the resolution note as its
|
|
1003
|
+
# `doing`. Regression for the bearings render bug where such a secondmate showed
|
|
1004
|
+
# state=unknown with resolution prose. The one-owner keyed fold in fm-classify-lib.sh
|
|
1005
|
+
# is untouched; this only stops the deriver from reading a non-state event as state.
|
|
1006
|
+
test_no_run_idle_secondmate_resolved_event_not_state() {
|
|
1007
|
+
reset_fakes
|
|
1008
|
+
local d; d=$(new_case resolved-idle)
|
|
1009
|
+
mkdir -p "$d/wt"
|
|
1010
|
+
make_fakebin "$d" >/dev/null
|
|
1011
|
+
fm_write_meta "$d/state/mate.meta" "window=fm:fm-mate" "worktree=$d/wt" "kind=secondmate" "home=$d/wt"
|
|
1012
|
+
printf 'needs-decision [key=race]: pick subscribe order\n' > "$d/state/mate.status"
|
|
1013
|
+
printf 'resolved [key=race]: went with subscribe-before-write\n' >> "$d/state/mate.status"
|
|
1014
|
+
FM_FAKE_AXI_STATUS=""
|
|
1015
|
+
FM_FAKE_BUSY=0
|
|
1016
|
+
local out; out=$(run_crew_state "$d" mate)
|
|
1017
|
+
assert_contains "$out" "state: unknown" "resolved-then-idle secondmate is not a spurious run-state"
|
|
1018
|
+
assert_contains "$out" "source: none" "a resolved event is not treated as a status-log state source"
|
|
1019
|
+
assert_not_contains "$out" "subscribe-before-write" "resolution prose must not leak into the detail"
|
|
1020
|
+
# A bare (non-keyed) resolved: closes the default key and behaves the same.
|
|
1021
|
+
printf 'blocked: waiting on infra\nresolved: infra access granted\n' > "$d/state/mate.status"
|
|
1022
|
+
out=$(run_crew_state "$d" mate)
|
|
1023
|
+
assert_contains "$out" "source: none" "a bare resolved: is not a state source either"
|
|
1024
|
+
assert_not_contains "$out" "infra access granted" "bare resolution prose must not leak into the detail"
|
|
1025
|
+
# Control: a genuine trailing state verb still renders from the log.
|
|
1026
|
+
printf 'working: reconciling routed items\n' > "$d/state/mate.status"
|
|
1027
|
+
out=$(run_crew_state "$d" mate)
|
|
1028
|
+
assert_contains "$out" "state: working" "a real trailing state verb still renders"
|
|
1029
|
+
assert_contains "$out" "reconciling routed items" "a real state line still carries its detail"
|
|
1030
|
+
pass "a trailing resolved: event does not corrupt state render (idle stays idle)"
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
test_dead_window_ignores_stale_status_log() {
|
|
1034
|
+
reset_fakes
|
|
1035
|
+
local d; d=$(new_case dead-window)
|
|
1036
|
+
make_repo_on_branch "$d/wt" fm/feat-dead
|
|
1037
|
+
make_fakebin "$d" >/dev/null
|
|
1038
|
+
fm_write_meta "$d/state/feat-dead.meta" "window=fm:fm-feat-dead" "worktree=$d/wt" "kind=ship"
|
|
1039
|
+
printf 'done: old completion event\n' > "$d/state/feat-dead.status"
|
|
1040
|
+
FM_FAKE_AXI_STATUS=""
|
|
1041
|
+
FM_FAKE_RUNS_LIST=""
|
|
1042
|
+
FM_FAKE_TMUX_MISSING=1
|
|
1043
|
+
local out; out=$(run_crew_state "$d" feat-dead)
|
|
1044
|
+
assert_contains "$out" "state: unknown" "dead window -> unknown"
|
|
1045
|
+
assert_contains "$out" "source: none" "dead window -> none source"
|
|
1046
|
+
assert_not_contains "$out" "source: status-log" "dead window does not reuse stale log"
|
|
1047
|
+
pass "dead window ignores stale status log"
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
# A closed/unreadable pane must NOT mask an authoritative run-step: judge by the
|
|
1051
|
+
# run-step, not the shell. The common case is a finished crew whose agent has
|
|
1052
|
+
# exited and closed its window (the normal gap between completion and teardown) -
|
|
1053
|
+
# it must still report its terminal run-step state (e.g. done), never unknown.
|
|
1054
|
+
test_dead_window_still_reports_terminal_run_step() {
|
|
1055
|
+
reset_fakes
|
|
1056
|
+
local d; d=$(new_case dead-window-done)
|
|
1057
|
+
make_repo_on_branch "$d/wt" fm/feat-dead-done
|
|
1058
|
+
make_fakebin "$d" >/dev/null
|
|
1059
|
+
fm_write_meta "$d/state/feat-dead-done.meta" "window=fm:fm-feat-dead-done" "worktree=$d/wt" "kind=ship"
|
|
1060
|
+
printf 'done: PR https://github.com/o/r/pull/3 checks green\n' > "$d/state/feat-dead-done.status"
|
|
1061
|
+
FM_FAKE_AXI_STATUS="$(run_passed fm/feat-dead-done)"
|
|
1062
|
+
FM_FAKE_TMUX_MISSING=1 # the crew's window has closed
|
|
1063
|
+
local out; out=$(run_crew_state "$d" feat-dead-done)
|
|
1064
|
+
assert_contains "$out" "state: done" "closed pane still reports terminal run-step done"
|
|
1065
|
+
assert_contains "$out" "source: run-step" "closed pane does not mask the run-step"
|
|
1066
|
+
assert_not_contains "$out" "state: unknown" "closed pane with a run must never be unknown"
|
|
1067
|
+
pass "closed pane still reports a terminal run-step"
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
# The same for an active run: an agent pane that crashed mid-validation while the
|
|
1071
|
+
# daemon-backed run continues must report the live run-step, not unknown.
|
|
1072
|
+
test_dead_window_still_reports_active_run_step() {
|
|
1073
|
+
reset_fakes
|
|
1074
|
+
local d; d=$(new_case dead-window-active)
|
|
1075
|
+
make_repo_on_branch "$d/wt" fm/feat-dead-act
|
|
1076
|
+
make_fakebin "$d" >/dev/null
|
|
1077
|
+
fm_write_meta "$d/state/feat-dead-act.meta" "window=fm:fm-feat-dead-act" "worktree=$d/wt" "kind=ship"
|
|
1078
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/feat-dead-act)"
|
|
1079
|
+
FM_FAKE_TMUX_MISSING=1
|
|
1080
|
+
local out; out=$(run_crew_state "$d" feat-dead-act)
|
|
1081
|
+
assert_contains "$out" "state: working" "closed pane still reports active run-step"
|
|
1082
|
+
assert_contains "$out" "source: run-step" "closed pane does not mask the active run-step"
|
|
1083
|
+
assert_not_contains "$out" "state: unknown" "closed pane with an active run must never be unknown"
|
|
1084
|
+
pass "closed pane still reports an active run-step"
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
test_no_timeout_uses_perl_bound() {
|
|
1088
|
+
reset_fakes
|
|
1089
|
+
local d toolbin out start elapsed calls_file calls
|
|
1090
|
+
d=$(new_case no-timeout)
|
|
1091
|
+
make_repo_on_branch "$d/wt" fm/feat-timeout
|
|
1092
|
+
make_fakebin "$d" >/dev/null
|
|
1093
|
+
calls_file="$d/no-mistakes.calls"
|
|
1094
|
+
: > "$calls_file"
|
|
1095
|
+
cat > "$d/fakebin/no-mistakes" <<'SH'
|
|
1096
|
+
#!/usr/bin/env bash
|
|
1097
|
+
printf '%s\n' "$*" >> "${FM_FAKE_NM_CALLS:-/dev/null}"
|
|
1098
|
+
while :; do :; done
|
|
1099
|
+
SH
|
|
1100
|
+
chmod +x "$d/fakebin/no-mistakes"
|
|
1101
|
+
toolbin=$(make_no_timeout_toolbin "$d")
|
|
1102
|
+
fm_write_meta "$d/state/feat-timeout.meta" "window=fm:fm-feat-timeout" "worktree=$d/wt" "kind=ship" \
|
|
1103
|
+
"harness=claude"
|
|
1104
|
+
FM_FAKE_BUSY=1
|
|
1105
|
+
local gen; gen=$("$ROOT/bin/fm-busy-event.sh" arm "$d/state" feat-timeout)
|
|
1106
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$d/state" feat-timeout busy --gen "$gen" \
|
|
1107
|
+
--source claude-hook --event user-prompt-submit
|
|
1108
|
+
start=$SECONDS
|
|
1109
|
+
out=$(FM_FAKE_NM_CALLS="$calls_file" PATH="$d/fakebin:$toolbin" FM_STATE_OVERRIDE="$d/state" FM_CREW_STATE_NM_TIMEOUT=1 "$CREW_STATE" feat-timeout)
|
|
1110
|
+
elapsed=$((SECONDS - start))
|
|
1111
|
+
assert_contains "$out" "state: working" "timed-out no-mistakes falls back to pane"
|
|
1112
|
+
assert_contains "$out" "source: pane" "timed-out no-mistakes -> pane source"
|
|
1113
|
+
[ "$elapsed" -lt 5 ] || fail "perl timeout did not bound no-mistakes calls (elapsed ${elapsed}s)"
|
|
1114
|
+
calls=$(awk 'END { print NR + 0 }' "$calls_file" 2>/dev/null || echo 0)
|
|
1115
|
+
[ "$calls" -eq 1 ] || fail "empty no-mistakes status triggered extra lookups ($calls calls)"
|
|
1116
|
+
pass "no timeout command uses perl bound"
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
# (i) kind=scout skips the run lookup entirely (its deliverable is a report).
|
|
1120
|
+
test_scout_skips_run_lookup() {
|
|
1121
|
+
reset_fakes
|
|
1122
|
+
local d; d=$(new_case scout)
|
|
1123
|
+
make_repo_on_branch "$d/wt" fm/scout-j
|
|
1124
|
+
make_fakebin "$d" >/dev/null
|
|
1125
|
+
fm_write_meta "$d/state/scout-j.meta" "window=fm:fm-scout-j" "worktree=$d/wt" "kind=scout" \
|
|
1126
|
+
"harness=claude"
|
|
1127
|
+
# Even if a run existed on this branch, a scout must not read it.
|
|
1128
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/scout-j)"
|
|
1129
|
+
FM_FAKE_BUSY=1
|
|
1130
|
+
local gen; gen=$("$ROOT/bin/fm-busy-event.sh" arm "$d/state" scout-j)
|
|
1131
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$d/state" scout-j busy --gen "$gen" \
|
|
1132
|
+
--source claude-hook --event user-prompt-submit
|
|
1133
|
+
local out; out=$(run_crew_state "$d" scout-j)
|
|
1134
|
+
assert_not_contains "$out" "source: run-step" "scout ignores no-mistakes run-step"
|
|
1135
|
+
assert_contains "$out" "source: pane" "scout reads its semantic busy state"
|
|
1136
|
+
pass "scout skips the run lookup"
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
# (j) torn-down worktree and missing meta are graceful (unknown/none, exit 0)
|
|
1140
|
+
test_torn_down_worktree() {
|
|
1141
|
+
reset_fakes
|
|
1142
|
+
local d; d=$(new_case torndown)
|
|
1143
|
+
make_fakebin "$d" >/dev/null
|
|
1144
|
+
fm_write_meta "$d/state/gone-k.meta" "window=fm:fm-gone-k" "worktree=$d/no-such-worktree" "kind=ship"
|
|
1145
|
+
local out rc
|
|
1146
|
+
out=$(run_crew_state "$d" gone-k); rc=$?
|
|
1147
|
+
expect_code 0 "$rc" "torn-down worktree exits 0"
|
|
1148
|
+
assert_contains "$out" "state: unknown" "torn-down -> unknown"
|
|
1149
|
+
assert_contains "$out" "source: none" "torn-down -> none source"
|
|
1150
|
+
pass "torn-down worktree is handled gracefully"
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
# --- remote secondmate arm ---------------------------------------------------
|
|
1154
|
+
# A meta recording remote_host= must never be read through the local worktree
|
|
1155
|
+
# probe or a local backend adapter: the recorded worktree and pane live on the
|
|
1156
|
+
# remote host, and the old local reads misreported a healthy remote mate as
|
|
1157
|
+
# "worktree gone". These cases drive the real helper over the real fm-on.sh
|
|
1158
|
+
# route with a stubbed ssh transport (FM_SSH_BIN seam): the stub prints
|
|
1159
|
+
# FM_FAKE_REMOTE_STATE_OUT as the remote endpoint's recovery-grade state and
|
|
1160
|
+
# exits FM_FAKE_SSH_RC.
|
|
1161
|
+
|
|
1162
|
+
setup_remote_case() { # <name> -> echoes case dir with remote meta + registry
|
|
1163
|
+
local d
|
|
1164
|
+
d=$(new_case "$1")
|
|
1165
|
+
mkdir -p "$d/data" "$d/fakebin"
|
|
1166
|
+
fm_write_meta "$d/state/rsm.meta" \
|
|
1167
|
+
"window=remote:rsm" \
|
|
1168
|
+
"endpoint_task_id=rsm" \
|
|
1169
|
+
"worktree=/remote/home/never-locally-present" \
|
|
1170
|
+
"harness=claude" \
|
|
1171
|
+
"kind=secondmate" \
|
|
1172
|
+
"mode=secondmate" \
|
|
1173
|
+
"remote_host=remote-mac" \
|
|
1174
|
+
"remote_root=/remote/root" \
|
|
1175
|
+
"remote_backend=herdr" \
|
|
1176
|
+
"remote_herdr_session=fm-remote" \
|
|
1177
|
+
"remote_target=fm-remote:w1:p1"
|
|
1178
|
+
cat > "$d/data/secondmates.md" <<EOF
|
|
1179
|
+
- rsm - remote test domain (host: remote-mac; root: /remote/root; home: /remote/home; scope: remote testing; projects: alpha; added 2026-08-02)
|
|
1180
|
+
EOF
|
|
1181
|
+
cat > "$d/fakebin/fake-ssh" <<'SH'
|
|
1182
|
+
#!/usr/bin/env bash
|
|
1183
|
+
cat > /dev/null
|
|
1184
|
+
[ -z "${FM_FAKE_REMOTE_STATE_OUT:-}" ] || printf '%s\n' "$FM_FAKE_REMOTE_STATE_OUT"
|
|
1185
|
+
exit "${FM_FAKE_SSH_RC:-0}"
|
|
1186
|
+
SH
|
|
1187
|
+
chmod +x "$d/fakebin/fake-ssh"
|
|
1188
|
+
printf '%s\n' "$d"
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
run_remote_crew_state() { # <case-dir> <id>
|
|
1192
|
+
PATH="$1/fakebin:$PATH" FM_HOME="$1" FM_STATE_OVERRIDE="$1/state" \
|
|
1193
|
+
FM_SSH_BIN="$1/fakebin/fake-ssh" "$CREW_STATE" "$2"
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
test_remote_alive_with_log_uses_status_log() {
|
|
1197
|
+
reset_fakes
|
|
1198
|
+
local d out rc
|
|
1199
|
+
d=$(setup_remote_case remote-alive-log)
|
|
1200
|
+
make_fakebin "$d" >/dev/null
|
|
1201
|
+
printf 'working: refactoring the quota adapter\n' > "$d/state/rsm.status"
|
|
1202
|
+
out=$(FM_FAKE_REMOTE_STATE_OUT=alive FM_FAKE_SSH_RC=0 run_remote_crew_state "$d" rsm); rc=$?
|
|
1203
|
+
expect_code 0 "$rc" "remote alive exits 0"
|
|
1204
|
+
assert_contains "$out" "state: working" "alive remote mate with a working log reads working"
|
|
1205
|
+
assert_contains "$out" "source: status-log" "alive remote mate reads current activity from the routed log"
|
|
1206
|
+
assert_contains "$out" "remote endpoint alive on remote-mac" "the remote liveness read should be visible"
|
|
1207
|
+
assert_not_contains "$out" "worktree gone" "a healthy remote mate must never read as torn down"
|
|
1208
|
+
pass "fm-crew-state remote: alive endpoint falls through to the routed status log"
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
test_remote_alive_idle_is_healthy_not_gone() {
|
|
1212
|
+
reset_fakes
|
|
1213
|
+
local d out rc
|
|
1214
|
+
d=$(setup_remote_case remote-alive-idle)
|
|
1215
|
+
make_fakebin "$d" >/dev/null
|
|
1216
|
+
out=$(FM_FAKE_REMOTE_STATE_OUT=alive FM_FAKE_SSH_RC=0 run_remote_crew_state "$d" rsm); rc=$?
|
|
1217
|
+
expect_code 0 "$rc" "remote alive-idle exits 0"
|
|
1218
|
+
assert_contains "$out" "source: remote-endpoint" "the remote endpoint is the reported source"
|
|
1219
|
+
assert_contains "$out" "alive on remote-mac" "an idle remote mate reads alive"
|
|
1220
|
+
assert_not_contains "$out" "worktree gone" "a healthy remote mate must never read as torn down"
|
|
1221
|
+
assert_not_contains "$out" "backend target gone" "a healthy remote mate must never read as a dead target"
|
|
1222
|
+
pass "fm-crew-state remote: an idle alive endpoint reads alive, never gone or dead"
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
test_remote_unreachable_is_unknown_remote_not_dead() {
|
|
1226
|
+
reset_fakes
|
|
1227
|
+
local d out rc
|
|
1228
|
+
d=$(setup_remote_case remote-unreachable)
|
|
1229
|
+
make_fakebin "$d" >/dev/null
|
|
1230
|
+
printf 'working: refactoring the quota adapter\n' > "$d/state/rsm.status"
|
|
1231
|
+
out=$(FM_FAKE_SSH_RC=255 run_remote_crew_state "$d" rsm); rc=$?
|
|
1232
|
+
expect_code 0 "$rc" "unreachable remote exits 0"
|
|
1233
|
+
assert_contains "$out" "unknown-remote" "an unreachable remote must be labeled unknown-remote"
|
|
1234
|
+
assert_contains "$out" "not proof of death" "an unreachable remote must not read as dead"
|
|
1235
|
+
assert_not_contains "$out" "worktree gone" "an unreachable remote must never read as torn down"
|
|
1236
|
+
assert_not_contains "$out" "backend target gone" "an unreachable remote must never read as a dead target"
|
|
1237
|
+
pass "fm-crew-state remote: an unreachable host reads unknown-remote, never gone or dead"
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
test_remote_dead_reports_remote_verdict() {
|
|
1241
|
+
reset_fakes
|
|
1242
|
+
local d out rc
|
|
1243
|
+
d=$(setup_remote_case remote-dead)
|
|
1244
|
+
make_fakebin "$d" >/dev/null
|
|
1245
|
+
out=$(FM_FAKE_REMOTE_STATE_OUT=dead FM_FAKE_SSH_RC=0 run_remote_crew_state "$d" rsm); rc=$?
|
|
1246
|
+
expect_code 0 "$rc" "remote dead exits 0"
|
|
1247
|
+
assert_contains "$out" "remote endpoint dead on remote-mac" \
|
|
1248
|
+
"a genuinely dead remote endpoint reports the remote host's own verdict"
|
|
1249
|
+
pass "fm-crew-state remote: the remote host's own dead verdict is reported truthfully"
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
test_missing_meta() {
|
|
1253
|
+
reset_fakes
|
|
1254
|
+
local d; d=$(new_case nometa)
|
|
1255
|
+
make_fakebin "$d" >/dev/null
|
|
1256
|
+
local out rc
|
|
1257
|
+
out=$(run_crew_state "$d" ghost-z); rc=$?
|
|
1258
|
+
expect_code 0 "$rc" "missing meta exits 0"
|
|
1259
|
+
assert_contains "$out" "state: unknown" "missing meta -> unknown"
|
|
1260
|
+
assert_contains "$out" "source: none" "missing meta -> none source"
|
|
1261
|
+
pass "missing meta is handled gracefully"
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
# (k) crew_is_provably_working end-to-end over the REAL fm-crew-state.sh (not a
|
|
1265
|
+
# canned fake verdict, unlike tests/fm-watch-triage.test.sh's classifier
|
|
1266
|
+
# coverage). This is the direct regression pair for the 2026-07-02 herdr
|
|
1267
|
+
# incident: a validating crew whose bare `axi status` answer belongs to
|
|
1268
|
+
# another branch must still be absorbed by the watcher via the runs-list
|
|
1269
|
+
# fallback (working), while a crew with genuinely no run anywhere and an idle
|
|
1270
|
+
# pane must still surface (the safety property the fix must never widen away).
|
|
1271
|
+
test_provably_working_via_runs_list_fallback() {
|
|
1272
|
+
reset_fakes
|
|
1273
|
+
local d short; d=$(new_case provably-working-crossbranch)
|
|
1274
|
+
make_repo_on_branch "$d/wt" fm/feat-provable
|
|
1275
|
+
short=$(git -C "$d/wt" rev-parse --short=7 HEAD)
|
|
1276
|
+
make_fakebin "$d" >/dev/null
|
|
1277
|
+
fm_write_meta "$d/state/feat-provable.meta" "window=fm:fm-feat-provable" "worktree=$d/wt" "kind=ship"
|
|
1278
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/other-crew)"
|
|
1279
|
+
FM_FAKE_RUNS_LIST="$(cat <<EOF
|
|
1280
|
+
running fm/other-crew aaaaaaa 2026-07-02 22:10
|
|
1281
|
+
running fm/feat-provable ${short} 2026-07-02 22:05
|
|
1282
|
+
EOF
|
|
1283
|
+
)"
|
|
1284
|
+
PATH="$d/fakebin:$PATH" FM_STATE_OVERRIDE="$d/state" crew_is_provably_working feat-provable \
|
|
1285
|
+
|| fail "cross-branch attribution via the runs list was not treated as provably working"
|
|
1286
|
+
pass "crew_is_provably_working absorbs a validating crew found only via the runs-list fallback"
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
test_not_provably_working_when_stopped() {
|
|
1290
|
+
reset_fakes
|
|
1291
|
+
local d; d=$(new_case provably-working-stopped)
|
|
1292
|
+
make_repo_on_branch "$d/wt" fm/feat-stopped
|
|
1293
|
+
make_fakebin "$d" >/dev/null
|
|
1294
|
+
fm_write_meta "$d/state/feat-stopped.meta" "window=fm:fm-feat-stopped" "worktree=$d/wt" "kind=ship"
|
|
1295
|
+
# Repo-wide run belongs to someone else, and this branch has no row in the
|
|
1296
|
+
# runs list either (it never validated, or genuinely finished/stopped) - the
|
|
1297
|
+
# only remaining signal is the pane, which is idle.
|
|
1298
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/other-crew)"
|
|
1299
|
+
FM_FAKE_RUNS_LIST="$(cat <<'EOF'
|
|
1300
|
+
running fm/other-crew aaaaaaa 2026-07-02 22:10
|
|
1301
|
+
EOF
|
|
1302
|
+
)"
|
|
1303
|
+
FM_FAKE_BUSY=0
|
|
1304
|
+
PATH="$d/fakebin:$PATH" FM_STATE_OVERRIDE="$d/state" crew_is_provably_working feat-stopped \
|
|
1305
|
+
&& fail "a stopped crew with no run anywhere and an idle pane was treated as provably working"
|
|
1306
|
+
pass "crew_is_provably_working still surfaces a genuinely stopped crew (safety property preserved)"
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
# Usage error (no id) is the one non-zero exit.
|
|
1310
|
+
test_usage_error() {
|
|
1311
|
+
reset_fakes
|
|
1312
|
+
local rc
|
|
1313
|
+
"$CREW_STATE" >/dev/null 2>&1; rc=$?
|
|
1314
|
+
expect_code 2 "$rc" "no-arg usage error exits 2"
|
|
1315
|
+
pass "usage error exits 2"
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
# Head-binding: same branch name with a rewritten/diverged worktree tip must not
|
|
1319
|
+
# attribute a historical no-mistakes run (multi-stage branch reuse incident).
|
|
1320
|
+
test_historical_same_branch_rewritten_head_not_current() {
|
|
1321
|
+
reset_fakes
|
|
1322
|
+
local d old_head new_head out
|
|
1323
|
+
d=$(new_case rewritten-head)
|
|
1324
|
+
make_repo_on_branch "$d/wt" fm/todo-flag
|
|
1325
|
+
old_head=$(git -C "$d/wt" rev-parse HEAD)
|
|
1326
|
+
# Simulate a rebase rewrite: orphan new history on the same branch name.
|
|
1327
|
+
git -C "$d/wt" checkout -q --orphan tmp-rewrite
|
|
1328
|
+
git -C "$d/wt" commit -q --allow-empty -m 'rewritten tip'
|
|
1329
|
+
git -C "$d/wt" branch -q -M fm/todo-flag
|
|
1330
|
+
new_head=$(git -C "$d/wt" rev-parse HEAD)
|
|
1331
|
+
[ "$old_head" != "$new_head" ] || fail "rewrite did not produce a new head"
|
|
1332
|
+
make_fakebin "$d" >/dev/null
|
|
1333
|
+
fm_write_meta "$d/state/wishlist.meta" "window=fm:fm-wishlist" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
1334
|
+
printf 'working: stage 2 setup complete rebased onto merged #76\n' > "$d/state/wishlist.status"
|
|
1335
|
+
# Historical run still reports the pre-rewrite head on the reused branch.
|
|
1336
|
+
FM_FAKE_RUN_HEAD="$old_head"
|
|
1337
|
+
FM_FAKE_AXI_STATUS="$(run_parked fm/todo-flag)"
|
|
1338
|
+
FM_FAKE_BUSY=0
|
|
1339
|
+
arm_idle_record "$d/state" wishlist
|
|
1340
|
+
out=$(run_crew_state "$d" wishlist)
|
|
1341
|
+
assert_not_contains "$out" "source: run-step" "historical rewritten head must not use run-step"
|
|
1342
|
+
assert_not_contains "$out" "parked at" "historical parked run must not mask current state"
|
|
1343
|
+
assert_contains "$out" "source: status-log" "falls back to status-log after head mismatch"
|
|
1344
|
+
assert_contains "$out" "state: working" "status-log working: remains current"
|
|
1345
|
+
pass "historical same-branch rewritten head is not attributed as current"
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
# Head-binding: an active pipeline whose run head is a descendant of the local
|
|
1349
|
+
# tip (fix commits on the same history) remains current.
|
|
1350
|
+
test_active_run_descendant_fix_head_remains_current() {
|
|
1351
|
+
reset_fakes
|
|
1352
|
+
local d base_head fix_head out
|
|
1353
|
+
d=$(new_case pipeline-descendant)
|
|
1354
|
+
make_repo_on_branch "$d/wt" fm/feat-pipeline
|
|
1355
|
+
base_head=$(git -C "$d/wt" rev-parse HEAD)
|
|
1356
|
+
git -C "$d/wt" commit -q --allow-empty -m 'pipeline fix commit'
|
|
1357
|
+
fix_head=$(git -C "$d/wt" rev-parse HEAD)
|
|
1358
|
+
# Worktree still at the pre-fix tip; run reports the pipeline fix head.
|
|
1359
|
+
git -C "$d/wt" reset -q --hard "$base_head"
|
|
1360
|
+
make_fakebin "$d" >/dev/null
|
|
1361
|
+
fm_write_meta "$d/state/pipe.meta" "window=fm:fm-pipe" "worktree=$d/wt" "kind=ship"
|
|
1362
|
+
FM_FAKE_RUN_HEAD="$fix_head"
|
|
1363
|
+
FM_FAKE_AXI_STATUS="$(run_fixing fm/feat-pipeline)"
|
|
1364
|
+
out=$(run_crew_state "$d" pipe)
|
|
1365
|
+
assert_contains "$out" "source: run-step" "descendant pipeline fix head remains run-step"
|
|
1366
|
+
assert_contains "$out" "state: working" "active fixing run remains working"
|
|
1367
|
+
pass "active run with valid descendant fix head remains current"
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
# Head-binding: local work that advanced past the run head invalidates the run.
|
|
1371
|
+
test_local_advanced_past_run_head_invalidates() {
|
|
1372
|
+
reset_fakes
|
|
1373
|
+
local d run_head out
|
|
1374
|
+
d=$(new_case local-advanced)
|
|
1375
|
+
make_repo_on_branch "$d/wt" fm/feat-adv
|
|
1376
|
+
run_head=$(git -C "$d/wt" rev-parse HEAD)
|
|
1377
|
+
git -C "$d/wt" commit -q --allow-empty -m 'local stage-2 work after prior run'
|
|
1378
|
+
make_fakebin "$d" >/dev/null
|
|
1379
|
+
fm_write_meta "$d/state/adv.meta" "window=fm:fm-adv" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
1380
|
+
printf 'working: stage 2 implementation in progress\n' > "$d/state/adv.status"
|
|
1381
|
+
FM_FAKE_RUN_HEAD="$run_head"
|
|
1382
|
+
FM_FAKE_AXI_STATUS="$(run_parked fm/feat-adv)"
|
|
1383
|
+
FM_FAKE_BUSY=0
|
|
1384
|
+
arm_idle_record "$d/state" adv
|
|
1385
|
+
out=$(run_crew_state "$d" adv)
|
|
1386
|
+
assert_not_contains "$out" "source: run-step" "local-advanced tip must not use historical run"
|
|
1387
|
+
assert_contains "$out" "source: status-log" "falls back after local advanced past run"
|
|
1388
|
+
assert_contains "$out" "state: working" "status-log working: is current"
|
|
1389
|
+
pass "local work advanced past run head invalidates attribution"
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
# --- Run-attribution precedence for pipeline-owned lane heads ----------------
|
|
1393
|
+
# A live run whose pipeline OWNS the branch (branch_sync.state=pipeline_owned)
|
|
1394
|
+
# can report a lane head that is not a git object in the task worktree.
|
|
1395
|
+
# Every fixture head is deliberately unresolvable so only the top-level
|
|
1396
|
+
# branch_sync exemption - never an accidental nested-field match - attributes
|
|
1397
|
+
# the run.
|
|
1398
|
+
run_running_pipeline_owned() { # <branch> <head> [<sync-state>]
|
|
1399
|
+
cat <<EOF
|
|
1400
|
+
run:
|
|
1401
|
+
id: "01RUNLIVE"
|
|
1402
|
+
branch: $1
|
|
1403
|
+
status: running
|
|
1404
|
+
head: "$2"
|
|
1405
|
+
pr: ""
|
|
1406
|
+
findings: none
|
|
1407
|
+
steps[2]{step,status,findings,duration_ms}:
|
|
1408
|
+
intent,completed,0,0
|
|
1409
|
+
review,running,0,0
|
|
1410
|
+
branch_sync:
|
|
1411
|
+
state: ${3:-pipeline_owned}
|
|
1412
|
+
changed: false
|
|
1413
|
+
local:
|
|
1414
|
+
branch: $1
|
|
1415
|
+
head: "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"
|
|
1416
|
+
clean: true
|
|
1417
|
+
next_action:
|
|
1418
|
+
code: continue_active_run
|
|
1419
|
+
command: no-mistakes axi status
|
|
1420
|
+
EOF
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
# T1 direction 1: the daemon-attributed ACTIVE pipeline-owned run binds without
|
|
1424
|
+
# head equality and wins over the older superseded failed row.
|
|
1425
|
+
test_pipeline_owned_active_run_beats_superseded_failed_row() {
|
|
1426
|
+
reset_fakes
|
|
1427
|
+
local d short; d=$(new_case f10-pipeline-owned)
|
|
1428
|
+
make_repo_on_branch "$d/wt" fm/feat-f10
|
|
1429
|
+
short=$(git -C "$d/wt" rev-parse --short=8 HEAD)
|
|
1430
|
+
make_fakebin "$d" >/dev/null
|
|
1431
|
+
fm_write_meta "$d/state/feat-f10.meta" "window=fm:fm-feat-f10" "worktree=$d/wt" "kind=ship"
|
|
1432
|
+
FM_FAKE_AXI_STATUS="$(run_running_pipeline_owned fm/feat-f10 f0f0f0f0)"
|
|
1433
|
+
FM_FAKE_RUNS_LIST="$(cat <<EOF
|
|
1434
|
+
running fm/feat-f10 f0f0f0f0 2026-08-27 13:53
|
|
1435
|
+
failed fm/feat-f10 ${short} 2026-08-27 12:09
|
|
1436
|
+
EOF
|
|
1437
|
+
)"
|
|
1438
|
+
local out; out=$(run_crew_state "$d" feat-f10)
|
|
1439
|
+
assert_contains "$out" "state: working" "pipeline-owned live run -> working"
|
|
1440
|
+
assert_contains "$out" "source: run-step" "pipeline-owned live run -> run-step source"
|
|
1441
|
+
assert_not_contains "$out" "state: failed" "superseded failed row must not surface over the live run"
|
|
1442
|
+
pass "pipeline-owned active run binds without head equality and beats the failed row"
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
# T1 direction 2: a genuinely-failed run with NO later run on the branch still
|
|
1446
|
+
# surfaces as failed - hiding real failures is equally wrong.
|
|
1447
|
+
test_failed_run_with_no_later_run_still_surfaces() {
|
|
1448
|
+
reset_fakes
|
|
1449
|
+
local d short; d=$(new_case f10-genuine-failure)
|
|
1450
|
+
make_repo_on_branch "$d/wt" fm/feat-f10b
|
|
1451
|
+
short=$(git -C "$d/wt" rev-parse --short=8 HEAD)
|
|
1452
|
+
make_fakebin "$d" >/dev/null
|
|
1453
|
+
fm_write_meta "$d/state/feat-f10b.meta" "window=fm:fm-feat-f10b" "worktree=$d/wt" "kind=ship"
|
|
1454
|
+
FM_FAKE_AXI_STATUS="$(run_failed fm/feat-f10b)"
|
|
1455
|
+
FM_FAKE_RUNS_LIST=" failed fm/feat-f10b ${short} 2026-08-27 12:09"
|
|
1456
|
+
local out; out=$(run_crew_state "$d" feat-f10b)
|
|
1457
|
+
assert_contains "$out" "state: failed" "a genuinely failed run with no later run still reports failed"
|
|
1458
|
+
assert_contains "$out" "source: run-step" "the genuine failure is run-step sourced"
|
|
1459
|
+
pass "a genuinely failed run with no later run is not hidden"
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
# The coarse runs-list scan: an ACTIVE row for this branch at an unresolvable
|
|
1463
|
+
# head is unknown attribution and must STOP the scan, never fall through onto
|
|
1464
|
+
# the older failed row (axi status answers another branch here, so attribution
|
|
1465
|
+
# can only go through the coarse list).
|
|
1466
|
+
test_coarse_unresolvable_active_row_never_falls_to_older_row() {
|
|
1467
|
+
reset_fakes
|
|
1468
|
+
local d short; d=$(new_case f10-coarse-guard)
|
|
1469
|
+
make_repo_on_branch "$d/wt" fm/feat-f10c
|
|
1470
|
+
short=$(git -C "$d/wt" rev-parse --short=8 HEAD)
|
|
1471
|
+
make_fakebin "$d" >/dev/null
|
|
1472
|
+
fm_write_meta "$d/state/feat-f10c.meta" "window=fm:fm-feat-f10c" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
1473
|
+
FM_FAKE_AXI_STATUS="$(run_running fm/other-crew)"
|
|
1474
|
+
FM_FAKE_RUNS_LIST="$(cat <<EOF
|
|
1475
|
+
running fm/other-crew aaaaaaa 2026-08-27 14:00
|
|
1476
|
+
running fm/feat-f10c f0f0f0f0 2026-08-27 13:53
|
|
1477
|
+
failed fm/feat-f10c ${short} 2026-08-27 12:09
|
|
1478
|
+
EOF
|
|
1479
|
+
)"
|
|
1480
|
+
FM_FAKE_BUSY=1
|
|
1481
|
+
local gen; gen=$("$ROOT/bin/fm-busy-event.sh" arm "$d/state" feat-f10c)
|
|
1482
|
+
"$ROOT/bin/fm-busy-event.sh" apply "$d/state" feat-f10c busy --gen "$gen" \
|
|
1483
|
+
--source claude-hook --event user-prompt-submit
|
|
1484
|
+
local out; out=$(run_crew_state "$d" feat-f10c)
|
|
1485
|
+
assert_not_contains "$out" "state: failed" "an unresolvable active row must not fall to the older failed row"
|
|
1486
|
+
assert_not_contains "$out" "source: run-step" "unknown attribution must not bind a run"
|
|
1487
|
+
assert_contains "$out" "state: working" "the busy crew still reads working through the pane fallback"
|
|
1488
|
+
assert_contains "$out" "source: pane" "unknown attribution falls to the pane, not an older row"
|
|
1489
|
+
pass "coarse scan stops on an unresolvable active row instead of binding an older one"
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
# Negative control: the exemption is gated on pipeline_owned specifically - any
|
|
1493
|
+
# other branch_sync state keeps the strict head rule.
|
|
1494
|
+
test_non_pipeline_owned_unresolvable_head_not_attributed() {
|
|
1495
|
+
reset_fakes
|
|
1496
|
+
local d; d=$(new_case f10-not-owned)
|
|
1497
|
+
make_repo_on_branch "$d/wt" fm/feat-f10d
|
|
1498
|
+
make_fakebin "$d" >/dev/null
|
|
1499
|
+
fm_write_meta "$d/state/feat-f10d.meta" "window=fm:fm-feat-f10d" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
1500
|
+
printf 'working: implementing\n' > "$d/state/feat-f10d.status"
|
|
1501
|
+
FM_FAKE_AXI_STATUS="$(run_running_pipeline_owned fm/feat-f10d f0f0f0f0 synced)"
|
|
1502
|
+
FM_FAKE_RUNS_LIST=""
|
|
1503
|
+
FM_FAKE_BUSY=0
|
|
1504
|
+
arm_idle_record "$d/state" feat-f10d
|
|
1505
|
+
local out; out=$(run_crew_state "$d" feat-f10d)
|
|
1506
|
+
assert_not_contains "$out" "source: run-step" "a non-pipeline-owned unresolvable head must not bind"
|
|
1507
|
+
assert_contains "$out" "source: status-log" "falls back to the status log without the exemption"
|
|
1508
|
+
pass "the exemption requires branch_sync.state=pipeline_owned"
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
# Negative control: the exemption also requires an ACTIVE run - a terminal run
|
|
1512
|
+
# released the branch, so an inconsistent pipeline_owned label must not bind a
|
|
1513
|
+
# terminal run by branch name alone.
|
|
1514
|
+
test_pipeline_owned_terminal_run_not_exempt() {
|
|
1515
|
+
reset_fakes
|
|
1516
|
+
local d; d=$(new_case f10-terminal-not-exempt)
|
|
1517
|
+
make_repo_on_branch "$d/wt" fm/feat-f10e
|
|
1518
|
+
make_fakebin "$d" >/dev/null
|
|
1519
|
+
fm_write_meta "$d/state/feat-f10e.meta" "window=fm:fm-feat-f10e" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
1520
|
+
printf 'working: stage 2 in progress\n' > "$d/state/feat-f10e.status"
|
|
1521
|
+
FM_FAKE_AXI_STATUS="$(run_running_pipeline_owned fm/feat-f10e f0f0f0f0)
|
|
1522
|
+
outcome: failed"
|
|
1523
|
+
FM_FAKE_RUNS_LIST=""
|
|
1524
|
+
FM_FAKE_BUSY=0
|
|
1525
|
+
arm_idle_record "$d/state" feat-f10e
|
|
1526
|
+
local out; out=$(run_crew_state "$d" feat-f10e)
|
|
1527
|
+
assert_not_contains "$out" "source: run-step" "a terminal run must not bind through the exemption"
|
|
1528
|
+
assert_contains "$out" "source: status-log" "falls back to the status log for a terminal unresolvable head"
|
|
1529
|
+
pass "the exemption never applies to a terminal run"
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
test_missing_run_head_falls_back_to_current_state() {
|
|
1533
|
+
reset_fakes
|
|
1534
|
+
local d out
|
|
1535
|
+
d=$(new_case missing-run-head)
|
|
1536
|
+
make_repo_on_branch "$d/wt" fm/feat-no-head
|
|
1537
|
+
make_fakebin "$d" >/dev/null
|
|
1538
|
+
fm_write_meta "$d/state/no-head.meta" "window=fm:fm-no-head" "worktree=$d/wt" "kind=ship" "harness=claude"
|
|
1539
|
+
printf 'working: current stage still in progress\n' > "$d/state/no-head.status"
|
|
1540
|
+
FM_FAKE_AXI_STATUS=$(run_parked fm/feat-no-head | grep -v '^ head:')
|
|
1541
|
+
FM_FAKE_RUNS_LIST=""
|
|
1542
|
+
FM_FAKE_BUSY=0
|
|
1543
|
+
arm_idle_record "$d/state" no-head
|
|
1544
|
+
out=$(run_crew_state "$d" no-head)
|
|
1545
|
+
assert_not_contains "$out" "source: run-step" "missing run head must not permit branch-only attribution"
|
|
1546
|
+
assert_contains "$out" "source: status-log" "missing run head falls back to current state sources"
|
|
1547
|
+
assert_contains "$out" "state: working" "status-log remains current after missing run head"
|
|
1548
|
+
pass "missing run head falls back instead of matching by branch"
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
test_active_run_is_authoritative
|
|
1552
|
+
test_stale_needs_decision_superseded
|
|
1553
|
+
test_stale_blocked_superseded
|
|
1554
|
+
test_genuine_parked_not_superseded
|
|
1555
|
+
test_scalar_gate_parked_not_superseded
|
|
1556
|
+
test_gate_block_parked_not_superseded
|
|
1557
|
+
test_ci_ready_done_log_beats_monitoring_run
|
|
1558
|
+
test_ci_monitoring_checks_green_surfaces_done
|
|
1559
|
+
test_top_level_ci_checks_green_surfaces_done
|
|
1560
|
+
test_ci_monitoring_no_checks_terminal_surfaces_done
|
|
1561
|
+
test_ci_monitoring_green_then_rearm_stays_working
|
|
1562
|
+
test_ci_monitoring_no_checks_yet_stays_working
|
|
1563
|
+
test_ci_monitoring_still_waiting_stays_working
|
|
1564
|
+
test_ci_monitoring_green_then_new_issue_stays_working
|
|
1565
|
+
test_ci_ready_done_log_relapse_stays_working
|
|
1566
|
+
test_ci_fixing_after_green_stays_working
|
|
1567
|
+
test_top_level_fixing_ci_running_after_green_stays_working
|
|
1568
|
+
test_top_level_fixing_done_log_stays_working
|
|
1569
|
+
test_terminal_passed
|
|
1570
|
+
test_terminal_failed
|
|
1571
|
+
test_cross_branch_attribution_via_runs_list
|
|
1572
|
+
test_cross_branch_attribution_picks_most_recent_row
|
|
1573
|
+
test_coarse_run_does_not_probe_other_branch_ci_log_for_ready_status
|
|
1574
|
+
test_other_branch_run_ignored
|
|
1575
|
+
test_no_run_busy_pane
|
|
1576
|
+
test_no_run_footer_text_alone_is_not_working
|
|
1577
|
+
test_no_run_grok_uses_isolated_fallback
|
|
1578
|
+
test_no_run_herdr_unknown_uses_backend_capture
|
|
1579
|
+
test_no_run_herdr_idle_agent_status_outranked_by_record
|
|
1580
|
+
test_no_run_herdr_idle_agent_status_and_idle_record_stays_idle
|
|
1581
|
+
test_no_run_idle_pane_uses_log
|
|
1582
|
+
test_no_run_idle_pane_uses_keyed_log
|
|
1583
|
+
test_no_run_idle_pane_paused
|
|
1584
|
+
test_no_run_idle_pane_custom_paused_verb
|
|
1585
|
+
test_no_run_idle_secondmate_resolved_event_not_state
|
|
1586
|
+
test_dead_window_ignores_stale_status_log
|
|
1587
|
+
test_dead_window_still_reports_terminal_run_step
|
|
1588
|
+
test_dead_window_still_reports_active_run_step
|
|
1589
|
+
test_no_timeout_uses_perl_bound
|
|
1590
|
+
test_scout_skips_run_lookup
|
|
1591
|
+
test_torn_down_worktree
|
|
1592
|
+
test_remote_alive_with_log_uses_status_log
|
|
1593
|
+
test_remote_alive_idle_is_healthy_not_gone
|
|
1594
|
+
test_remote_unreachable_is_unknown_remote_not_dead
|
|
1595
|
+
test_remote_dead_reports_remote_verdict
|
|
1596
|
+
test_missing_meta
|
|
1597
|
+
test_provably_working_via_runs_list_fallback
|
|
1598
|
+
test_not_provably_working_when_stopped
|
|
1599
|
+
test_usage_error
|
|
1600
|
+
test_historical_same_branch_rewritten_head_not_current
|
|
1601
|
+
test_active_run_descendant_fix_head_remains_current
|
|
1602
|
+
test_local_advanced_past_run_head_invalidates
|
|
1603
|
+
test_pipeline_owned_active_run_beats_superseded_failed_row
|
|
1604
|
+
test_failed_run_with_no_later_run_still_surfaces
|
|
1605
|
+
test_coarse_unresolvable_active_row_never_falls_to_older_row
|
|
1606
|
+
test_non_pipeline_owned_unresolvable_head_not_attributed
|
|
1607
|
+
test_pipeline_owned_terminal_run_not_exempt
|
|
1608
|
+
test_missing_run_head_falls_back_to_current_state
|
|
1609
|
+
|
|
1610
|
+
echo "all fm-crew-state tests passed"
|