@zerwiz/ymir 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/README.md +74 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +360 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +11 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +121 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +90 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +57 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +35 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/install.md +69 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +498 -0
- package/CHANGELOG.md +1913 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +114 -60
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +97 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +108 -0
- package/RULES/07-config.md +101 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +22 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +133 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +256 -0
- package/bin/apodex-smoke-test.sh +105 -0
- package/bin/bifrost-bridge.sh +131 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +112 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +51 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-assemble.sh +154 -0
- package/bin/changelog-guard.sh +197 -0
- package/bin/crash-sense.sh +173 -0
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +153 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +225 -0
- package/bin/eindri-acclaim.sh +47 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +54 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +98 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +158 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +81 -0
- package/bin/github-deploy.sh +94 -0
- package/bin/gjallarhorn-expose.sh +136 -0
- package/bin/gjallarhorn-notify.sh +71 -0
- package/bin/gjallarhorn-purge.sh +67 -0
- package/bin/gjallarhorn-tunnel.sh +85 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +130 -0
- package/bin/hall-snapshot.sh +111 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +452 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +105 -0
- package/bin/hodd.sh +102 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +121 -0
- package/bin/local-model-lock.sh +70 -0
- package/bin/mimir-bridge.py +259 -0
- package/bin/mimir-bridge.sh +100 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +72 -0
- package/bin/mjollnir.sh +104 -0
- package/bin/model-hardware.sh +96 -0
- package/bin/model-resolve.sh +160 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-hall-snapshot.sh +28 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +161 -0
- package/bin/omarchy-plugins.sh +187 -0
- package/bin/omarchy-sense.sh +162 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-model-agnostic.sh +71 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +58 -0
- package/bin/project-git.sh +69 -0
- package/bin/public-guard.sh +71 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +104 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +94 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +89 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +203 -0
- package/bin/telegram-bot.sh +82 -0
- package/bin/toolchain.sh +74 -0
- package/bin/ui-truth-check.sh +75 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +298 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +60 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +163 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +806 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-plan.sh +356 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +110 -0
- package/bin/ymir-setup-auth.sh +119 -0
- package/bin/ymir-validate.sh +193 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1060 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +54 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +269 -0
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +217 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,2191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fm-test-run.sh - single owner of Firstmate's behavior-test runner, lane
|
|
3
|
+
# composition for portable CI shards, local --jobs for proven-concurrent work,
|
|
4
|
+
# timing markers, and the complete-regression coverage guard.
|
|
5
|
+
#
|
|
6
|
+
# Selection modes (exactly one of: --all, --family, --changed, --lane,
|
|
7
|
+
# --proven-isolated, or script paths):
|
|
8
|
+
# fm-test-run.sh --all
|
|
9
|
+
# fm-test-run.sh --family <name>
|
|
10
|
+
# fm-test-run.sh --changed [--base <git-ref>]
|
|
11
|
+
# fm-test-run.sh --lane portable-parallel-1|portable-parallel-2|portable-serial
|
|
12
|
+
# fm-test-run.sh --lane portable-serial-<k>of<n> (one CI serial shard)
|
|
13
|
+
# fm-test-run.sh --proven-isolated
|
|
14
|
+
# fm-test-run.sh tests/<name>.test.sh [more scripts...]
|
|
15
|
+
#
|
|
16
|
+
# Inspection (no execution):
|
|
17
|
+
# fm-test-run.sh --list --all
|
|
18
|
+
# fm-test-run.sh --list --family <name>
|
|
19
|
+
# fm-test-run.sh --list --lane portable-parallel-1
|
|
20
|
+
# fm-test-run.sh --list-scheduled --family <name>
|
|
21
|
+
# fm-test-run.sh --list-families
|
|
22
|
+
# fm-test-run.sh --list-concurrent-safe-families
|
|
23
|
+
# fm-test-run.sh --concurrent-safe-family-jobs-max <name>
|
|
24
|
+
# fm-test-run.sh --list-lanes
|
|
25
|
+
# fm-test-run.sh --check-coverage
|
|
26
|
+
#
|
|
27
|
+
# Aggregation (no suite execution):
|
|
28
|
+
# fm-test-run.sh --aggregate-json <out.json> <lane.json> [more lane.json...]
|
|
29
|
+
#
|
|
30
|
+
# Options:
|
|
31
|
+
# --json <path> write a deterministic timing artifact after the run
|
|
32
|
+
# --list print selected script paths (one per line) and exit 0
|
|
33
|
+
# --list-scheduled
|
|
34
|
+
# print selected paths longest-hint-first and exit 0
|
|
35
|
+
# --base <ref> with --changed, compare against this ref (default: origin/main)
|
|
36
|
+
# --exclude-family <name>
|
|
37
|
+
# drop scripts whose primary family matches <name> after selection
|
|
38
|
+
# (repeatable; portable CI lanes exclude real-herdr-gated so the
|
|
39
|
+
# dedicated required Herdr lane owns that coverage)
|
|
40
|
+
# --fail-on-gate-skip <token>
|
|
41
|
+
# after each script, fail the run if any output line contains
|
|
42
|
+
# "skip: <token>" (e.g. --fail-on-gate-skip 'herdr not found').
|
|
43
|
+
# The required Herdr CI lane uses this so a missing pin cannot
|
|
44
|
+
# silently pass as a gate skip.
|
|
45
|
+
# --jobs N run the selected scripts with up to N concurrent workers.
|
|
46
|
+
# Plain --changed uses min(4, cpus) workers when multiple
|
|
47
|
+
# selected scripts are admissible.
|
|
48
|
+
# N>1 is allowed only when every selected script is proven
|
|
49
|
+
# safe to run concurrently: individually in the proven-isolated
|
|
50
|
+
# set (bin/fm-test-isolation-proof.sh --list), or in a family
|
|
51
|
+
# carrying a recorded concurrent proof
|
|
52
|
+
# (list_concurrent_safe_families below). Overall cap is 8;
|
|
53
|
+
# family proofs may impose a lower cap. Unproven stateful
|
|
54
|
+
# scripts stay serial. Concurrent runs are ordered
|
|
55
|
+
# longest-hint-first so the slowest script is not stranded
|
|
56
|
+
# alone at the tail. Default is 1 (serial) except for plain
|
|
57
|
+
# --changed, which uses the bounded automatic scheduler. Any
|
|
58
|
+
# unproven remainder runs serially after that group.
|
|
59
|
+
# --per-script-timeout-secs N
|
|
60
|
+
# terminate a script that runs longer than N seconds and
|
|
61
|
+
# record it as exit 124 (0 disables, the default). The
|
|
62
|
+
# --changed applies 900s automatically: no real script
|
|
63
|
+
# approaches it, so it only converts a HUNG
|
|
64
|
+
# script into a bounded failure. --max-wall-ms is checked
|
|
65
|
+
# after the run and so cannot catch a hang on its own.
|
|
66
|
+
# External interruption cleanup is outside this runner's
|
|
67
|
+
# guarantee; configured per-script bounds remain authoritative.
|
|
68
|
+
# --max-wall-ms N fail the run when its measured invocation wall clock exceeds
|
|
69
|
+
# N milliseconds, including an empty selection. It is
|
|
70
|
+
# evaluated after selection and suite execution and cannot
|
|
71
|
+
# interrupt a running script; per-script hangs are
|
|
72
|
+
# bounded by --per-script-timeout-secs. Pathological output
|
|
73
|
+
# sinks that block finalization are explicitly out of scope.
|
|
74
|
+
# -h, --help print this header
|
|
75
|
+
#
|
|
76
|
+
# Per-script machine-parseable markers (stdout):
|
|
77
|
+
# FM_TEST_BEGIN <iso8601> <script> family=<family> expected_gate_skip=<class>
|
|
78
|
+
# FM_TEST_END <iso8601> <script> exit=<code> duration_ms=<n> gate_skip=<true|false>
|
|
79
|
+
#
|
|
80
|
+
# After all scripts (stdout):
|
|
81
|
+
# FM_TEST_SUMMARY total=<n> failed=<n> skipped_gate=<n> duration_ms=<n>
|
|
82
|
+
# FM_TEST_SUMMARY_FAMILY family=<name> count=<n> duration_ms=<n> failed=<n>
|
|
83
|
+
# FM_TEST_SLOWEST rank=<k> script=<path> duration_ms=<n>
|
|
84
|
+
# FM_TEST_BUDGET max_wall_ms=<n> duration_ms=<n> (only with --max-wall-ms)
|
|
85
|
+
#
|
|
86
|
+
# Exit status is non-zero if any selected script exits non-zero, a configured
|
|
87
|
+
# --fail-on-gate-skip token appears, the measured duration exceeds
|
|
88
|
+
# --max-wall-ms, timing-artifact finalization fails, or a concurrent worker
|
|
89
|
+
# violates its isolation check. Other gate skips (first meaningful line
|
|
90
|
+
# matching ^skip:) remain successful and are counted as skipped_gate.
|
|
91
|
+
#
|
|
92
|
+
# Family labels, the changed-file map, and production portable-shard composition
|
|
93
|
+
# live in this script only (one owner). The proven-isolated candidate set remains
|
|
94
|
+
# owned by bin/fm-test-isolation-proof.sh; portable parallel shards are a
|
|
95
|
+
# duration-balanced partition of that exact set (see docs/fm-test-portable-shards.md).
|
|
96
|
+
#
|
|
97
|
+
# portable-serial stays strictly serial. Its CI shards (portable-serial-<k>of<n>)
|
|
98
|
+
# split it across separate runners, so two of its stateful scripts still never
|
|
99
|
+
# share a machine. This script owns <n>: a lane whose <n> disagrees with the
|
|
100
|
+
# configured shard count is refused, so a CI matrix cannot silently drop a shard.
|
|
101
|
+
# --changed is conservative: it over-selects related families rather than
|
|
102
|
+
# under-selecting, and never expands to the complete suite unless --all. The one
|
|
103
|
+
# place it is deliberately narrow is a bin/ path with no curated family: a test
|
|
104
|
+
# that names it is selected as that SCRIPT, because the reference is per-script
|
|
105
|
+
# evidence. Consumer bin/ scripts still resolve through the curated map, so
|
|
106
|
+
# recorded family-level coupling still expands to the whole family.
|
|
107
|
+
set -eu
|
|
108
|
+
|
|
109
|
+
now_ms() {
|
|
110
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
111
|
+
python3 -c 'import time; print(int(time.time() * 1000))'
|
|
112
|
+
else
|
|
113
|
+
echo $(($(date +%s) * 1000))
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
RUN_STARTED_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
118
|
+
RUN_STARTED_MS=$(now_ms)
|
|
119
|
+
|
|
120
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
121
|
+
cd "$ROOT" || exit 1
|
|
122
|
+
|
|
123
|
+
MODE=
|
|
124
|
+
LIST_ONLY=0
|
|
125
|
+
LIST_SCHEDULED=0
|
|
126
|
+
LIST_FAMILIES=0
|
|
127
|
+
LIST_CONCURRENT_SAFE_FAMILIES=0
|
|
128
|
+
LIST_LANES=0
|
|
129
|
+
CHECK_COVERAGE=0
|
|
130
|
+
AGGREGATE_OUT=
|
|
131
|
+
FAMILY=
|
|
132
|
+
LANE=
|
|
133
|
+
BASE_REF=origin/main
|
|
134
|
+
JSON_PATH=
|
|
135
|
+
SCRIPTS=()
|
|
136
|
+
EXCLUDE_FAMILIES=()
|
|
137
|
+
FAIL_ON_GATE_SKIP=
|
|
138
|
+
JOBS=1
|
|
139
|
+
JOBS_EXPLICIT=0
|
|
140
|
+
JOBS_MAX=8
|
|
141
|
+
MAX_WALL_MS=
|
|
142
|
+
PER_SCRIPT_TIMEOUT_SECS=0
|
|
143
|
+
# Bound applied automatically on the automatic --changed path, derived from
|
|
144
|
+
# measured healthy runtimes with margin rather than picked: the slowest measured
|
|
145
|
+
# behavior test is the 341s Herdr presentation E2E, and the slowest script in a
|
|
146
|
+
# runner-file changed selection is tests/fm-calm-pi-extension.test.sh at 77s
|
|
147
|
+
# once its Chrome reap terminates. 900s leaves roughly 2.6x headroom over the
|
|
148
|
+
# slowest real script, so this can only ever fire on a script that is genuinely
|
|
149
|
+
# stuck. It is a guard, not a speed control: a HUNG script becomes a bounded
|
|
150
|
+
# failure instead of an unbounded suite, which is the shape that silently
|
|
151
|
+
# outruns a caller's invocation budget.
|
|
152
|
+
CHANGED_DEFAULT_TIMEOUT_SECS=900
|
|
153
|
+
|
|
154
|
+
# How many separate-runner shards the portable serial remainder splits into.
|
|
155
|
+
# One owner: CI lane names carry this count and are refused when they disagree.
|
|
156
|
+
PORTABLE_SERIAL_SHARDS=4
|
|
157
|
+
|
|
158
|
+
# Balance hint for a portable-serial script with no measured duration, close to
|
|
159
|
+
# the measured per-script mean so a newly added test neither starves nor
|
|
160
|
+
# overloads the shard it lands in.
|
|
161
|
+
PORTABLE_SERIAL_DEFAULT_WEIGHT_MS=20000
|
|
162
|
+
|
|
163
|
+
usage() {
|
|
164
|
+
awk '
|
|
165
|
+
NR == 1 { next }
|
|
166
|
+
/^#/ { sub(/^# ?/, ""); print; next }
|
|
167
|
+
{ exit }
|
|
168
|
+
' "$0" >&2
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
die() {
|
|
172
|
+
printf 'fm-test-run: %s\n' "$*" >&2
|
|
173
|
+
exit 2
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
log() {
|
|
177
|
+
printf 'fm-test-run: %s\n' "$*" >&2
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
now_iso() {
|
|
181
|
+
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
cpu_count() {
|
|
185
|
+
local n
|
|
186
|
+
n=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)
|
|
187
|
+
case "$n" in
|
|
188
|
+
''|*[!0-9]*) n=1 ;;
|
|
189
|
+
esac
|
|
190
|
+
[ "$n" -ge 1 ] || n=1
|
|
191
|
+
printf '%s\n' "$n"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# Primary family for one tests/*.test.sh basename. Unmapped scripts are
|
|
195
|
+
# unclassified so new tests are still runnable and visible in summaries.
|
|
196
|
+
family_for_basename() {
|
|
197
|
+
case "$1" in
|
|
198
|
+
fm-arm-pretool-check.test.sh|fm-ask-user-authority.test.sh|\
|
|
199
|
+
fm-bearings-board.test.sh|\
|
|
200
|
+
fm-brief.test.sh|fm-vendor-auth-probe.test.sh|\
|
|
201
|
+
fm-calm-pi-extension.test.sh|fm-cd-pretool-check.test.sh|\
|
|
202
|
+
fm-classify-decision-key.test.sh|\
|
|
203
|
+
fm-composer-ghost.test.sh|fm-composer-lib.test.sh|\
|
|
204
|
+
fm-crew-state.test.sh|fm-captain-hold-lifecycle.test.sh|\
|
|
205
|
+
fm-documentation-audiences.test.sh|fm-ensure-agents-md.test.sh|fm-grok-harness.test.sh|\
|
|
206
|
+
fm-kimi-harness.test.sh|fm-muse-harness.test.sh|fm-herdr-lab.test.sh|fm-lint.test.sh|\
|
|
207
|
+
fm-lint-workflows.test.sh|\
|
|
208
|
+
fm-operational-input.test.sh|fm-pi-primary-types.test.sh|\
|
|
209
|
+
fm-harness-adapter-references.test.sh|\
|
|
210
|
+
fm-send-popup-settle.test.sh|fm-send-settle.test.sh|\
|
|
211
|
+
fm-subagent-pretool-check.test.sh|\
|
|
212
|
+
fm-supervision-instructions.test.sh|fm-task-delivery.test.sh|\
|
|
213
|
+
fm-tmux-submit-busy.test.sh|fm-trace-context-lib.test.sh|\
|
|
214
|
+
fm-transition-lib.test.sh|\
|
|
215
|
+
fm-test-run.test.sh|fm-test-isolation-proof.test.sh)
|
|
216
|
+
printf '%s\n' pure-contract-unit
|
|
217
|
+
;;
|
|
218
|
+
fm-daemon.test.sh|fm-guard-stale-banner.test.sh|fm-pi-watch-extension.test.sh|\
|
|
219
|
+
fm-session-lock-ancestry.test.sh|fm-cursor-primary.test.sh|\
|
|
220
|
+
fm-supervision-events.test.sh|fm-turnend-guard.test.sh|fm-wake-daemon-lifecycle-e2e.test.sh|\
|
|
221
|
+
fm-wake-drain-unread-status.test.sh|\
|
|
222
|
+
fm-tool-update-check.test.sh|\
|
|
223
|
+
fm-wake-queue.test.sh|fm-watch-arm.test.sh|fm-watch-checkpoint.test.sh|fm-watch-recovery-loop.test.sh|\
|
|
224
|
+
fm-watch-triage.test.sh|fm-task-inbox.test.sh|\
|
|
225
|
+
fm-watcher-lock.test.sh|fm-inactive-reconcile.test.sh)
|
|
226
|
+
printf '%s\n' watcher-wake-lock
|
|
227
|
+
;;
|
|
228
|
+
fm-afk-inject-herdr-e2e.test.sh|fm-afk-launch.test.sh|fm-backend-autodetect-smoke.test.sh|\
|
|
229
|
+
fm-backend-herdr-eventwait-smoke.test.sh|fm-backend-herdr-presentation-e2e.test.sh|\
|
|
230
|
+
fm-backend-herdr-launcher-workspace-e2e.test.sh|\
|
|
231
|
+
fm-backend-herdr-prune-safety-e2e.test.sh|fm-backend-herdr-respawn-idem-e2e.test.sh|\
|
|
232
|
+
fm-herdr-session-cleanup-e2e.test.sh|\
|
|
233
|
+
fm-backend-herdr-smoke.test.sh|fm-backend-herdr-workspace-per-home-e2e.test.sh|\
|
|
234
|
+
fm-control-herdr-smoke.test.sh)
|
|
235
|
+
printf '%s\n' real-herdr-gated
|
|
236
|
+
;;
|
|
237
|
+
fm-backlog-handoff.test.sh|fm-on.test.sh|fm-remote-backlog-handoff.test.sh|\
|
|
238
|
+
fm-remote-doctor.test.sh|fm-remote-job.test.sh|fm-remote-job-orphan-reap.test.sh|\
|
|
239
|
+
fm-remote-transport-lanes.test.sh|\
|
|
240
|
+
fm-remote-reply.test.sh|fm-remote-secondmate-lifecycle-e2e.test.sh|\
|
|
241
|
+
fm-remote-secondmate-trace-context.test.sh|\
|
|
242
|
+
fm-secondmate-harness.test.sh|fm-secondmate-lifecycle-e2e.test.sh|\
|
|
243
|
+
fm-secondmate-liveness.test.sh|fm-secondmate-reconcile.test.sh|\
|
|
244
|
+
fm-secondmate-safety.test.sh|fm-secondmate-sync.test.sh|\
|
|
245
|
+
fm-startup-memory-budget.test.sh|fm-stow-cascade.test.sh|\
|
|
246
|
+
fm-send-secondmate-marker.test.sh|fm-shared-captain-inheritance.test.sh)
|
|
247
|
+
printf '%s\n' secondmate
|
|
248
|
+
;;
|
|
249
|
+
fm-backlog-atomicity.test.sh|\
|
|
250
|
+
fm-bootstrap.test.sh|fm-bootstrap-network-parallel.test.sh|fm-fleet-sync.test.sh|fm-gate-refuse.test.sh|fm-gotmp.test.sh|\
|
|
251
|
+
fm-session-start.test.sh|fm-sessionstart-nudge.test.sh|fm-startup-network.test.sh|\
|
|
252
|
+
fm-tangle-guard.test.sh|fm-update.test.sh)
|
|
253
|
+
printf '%s\n' session-bootstrap
|
|
254
|
+
;;
|
|
255
|
+
fm-afk-pi-herdr-return-e2e.test.sh|\
|
|
256
|
+
fm-cmux-claude-composer-live-e2e.test.sh|\
|
|
257
|
+
fm-composer-matrix-live-e2e.test.sh|\
|
|
258
|
+
fm-codex-continuity-live-e2e.test.sh|fm-grok-continuity-live-e2e.test.sh|\
|
|
259
|
+
fm-cursor-primary-live-e2e.test.sh|\
|
|
260
|
+
fm-grok-stop-live-e2e.test.sh|fm-harness-adapter-instructions-live-e2e.test.sh|\
|
|
261
|
+
fm-harness-liveness-drift-live-e2e.test.sh|\
|
|
262
|
+
fm-muse-signals-live-e2e.test.sh|\
|
|
263
|
+
fm-herdr-version-floor-live-e2e.test.sh|\
|
|
264
|
+
fm-opencode-primary-live-e2e.test.sh|fm-pi-branch-live-e2e.test.sh|\
|
|
265
|
+
fm-pi-primary-live-e2e.test.sh|\
|
|
266
|
+
fm-sessionstart-hook-live-e2e.test.sh|fm-sessionstart-instruction-refresh-live-e2e.test.sh|\
|
|
267
|
+
fm-quota-array-dispatch-live-e2e.test.sh|fm-send-secondmate-marker-herdr-e2e.test.sh|\
|
|
268
|
+
fm-send-inbox-doorbell-live-e2e.test.sh|\
|
|
269
|
+
fm-herdr-submit-confirm-live-e2e.test.sh)
|
|
270
|
+
printf '%s\n' live-harness-optin
|
|
271
|
+
;;
|
|
272
|
+
fm-backend-herdr.test.sh|fm-backend-tmux-smoke.test.sh|fm-backend.test.sh|\
|
|
273
|
+
fm-tmux-agent-liveness.test.sh|\
|
|
274
|
+
fm-control.test.sh|fm-control-relaunch.test.sh|\
|
|
275
|
+
fm-herdr-session-cleanup.test.sh|fm-send-resolve-key.test.sh|fm-send-strict.test.sh|\
|
|
276
|
+
fm-send-inbox.test.sh|fm-spawn-batch.test.sh|\
|
|
277
|
+
fm-spawn-dispatch-profile.test.sh|\
|
|
278
|
+
fm-trace-context-spawn.test.sh|fm-spawn-worktree-settle.test.sh|\
|
|
279
|
+
fm-teardown-endpoint-safety.test.sh)
|
|
280
|
+
printf '%s\n' backend-dispatch
|
|
281
|
+
;;
|
|
282
|
+
fm-check-unregister.test.sh|fm-pr-check-security.test.sh|fm-pr-merge.test.sh|\
|
|
283
|
+
fm-review-diff.test.sh|fm-teardown.test.sh|fm-x-mode.test.sh)
|
|
284
|
+
printf '%s\n' pr-forge
|
|
285
|
+
;;
|
|
286
|
+
fm-afk-inject-e2e.test.sh|fm-afk-return.test.sh)
|
|
287
|
+
printf '%s\n' afk
|
|
288
|
+
;;
|
|
289
|
+
fm-bearings-board-render.test.sh|fm-bearings-snapshot.test.sh|\
|
|
290
|
+
fm-fleet-snapshot-view.test.sh|fm-home-summary-refresh.test.sh)
|
|
291
|
+
printf '%s\n' snapshot-bearings
|
|
292
|
+
;;
|
|
293
|
+
fm-backend-cmux.test.sh|fm-backend-cmux-smoke.test.sh)
|
|
294
|
+
printf '%s\n' cmux
|
|
295
|
+
;;
|
|
296
|
+
fm-backend-zellij.test.sh|fm-backend-zellij-smoke.test.sh)
|
|
297
|
+
printf '%s\n' zellij
|
|
298
|
+
;;
|
|
299
|
+
fm-backend-orca.test.sh)
|
|
300
|
+
printf '%s\n' orca
|
|
301
|
+
;;
|
|
302
|
+
*)
|
|
303
|
+
printf '%s\n' unclassified
|
|
304
|
+
;;
|
|
305
|
+
esac
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
expected_gate_skip_for_family() {
|
|
309
|
+
case "$1" in
|
|
310
|
+
real-herdr-gated) printf '%s\n' herdr ;;
|
|
311
|
+
live-harness-optin) printf '%s\n' optin-env ;;
|
|
312
|
+
cmux|zellij|orca) printf '%s\n' optional-binary ;;
|
|
313
|
+
snapshot-bearings) printf '%s\n' optional-binary ;;
|
|
314
|
+
*) printf '%s\n' none ;;
|
|
315
|
+
esac
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
list_known_families() {
|
|
319
|
+
cat <<'EOF'
|
|
320
|
+
pure-contract-unit
|
|
321
|
+
watcher-wake-lock
|
|
322
|
+
real-herdr-gated
|
|
323
|
+
secondmate
|
|
324
|
+
session-bootstrap
|
|
325
|
+
live-harness-optin
|
|
326
|
+
backend-dispatch
|
|
327
|
+
pr-forge
|
|
328
|
+
afk
|
|
329
|
+
snapshot-bearings
|
|
330
|
+
cmux
|
|
331
|
+
zellij
|
|
332
|
+
orca
|
|
333
|
+
unclassified
|
|
334
|
+
EOF
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
list_known_lanes() {
|
|
338
|
+
local i
|
|
339
|
+
printf '%s\n' portable-parallel-1
|
|
340
|
+
printf '%s\n' portable-parallel-2
|
|
341
|
+
printf '%s\n' portable-serial
|
|
342
|
+
i=1
|
|
343
|
+
while [ "$i" -le "$PORTABLE_SERIAL_SHARDS" ]; do
|
|
344
|
+
printf 'portable-serial-%sof%s\n' "$i" "$PORTABLE_SERIAL_SHARDS"
|
|
345
|
+
i=$((i + 1))
|
|
346
|
+
done
|
|
347
|
+
printf '%s\n' real-herdr-gated
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
# Exact proven-isolated candidate set (same paths as
|
|
351
|
+
# bin/fm-test-isolation-proof.sh --list). Do not expand without a new concurrent
|
|
352
|
+
# isolation proof archive.
|
|
353
|
+
list_proven_isolated() {
|
|
354
|
+
cat <<'EOF'
|
|
355
|
+
tests/fm-arm-pretool-check.test.sh
|
|
356
|
+
tests/fm-backend-herdr.test.sh
|
|
357
|
+
tests/fm-brief.test.sh
|
|
358
|
+
tests/fm-captain-hold-lifecycle.test.sh
|
|
359
|
+
tests/fm-cd-pretool-check.test.sh
|
|
360
|
+
tests/fm-composer-ghost.test.sh
|
|
361
|
+
tests/fm-composer-lib.test.sh
|
|
362
|
+
tests/fm-crew-state.test.sh
|
|
363
|
+
tests/fm-ensure-agents-md.test.sh
|
|
364
|
+
tests/fm-grok-harness.test.sh
|
|
365
|
+
tests/fm-herdr-lab.test.sh
|
|
366
|
+
tests/fm-lint.test.sh
|
|
367
|
+
tests/fm-pi-primary-types.test.sh
|
|
368
|
+
tests/fm-pr-merge.test.sh
|
|
369
|
+
tests/fm-review-diff.test.sh
|
|
370
|
+
tests/fm-send-popup-settle.test.sh
|
|
371
|
+
tests/fm-send-settle.test.sh
|
|
372
|
+
tests/fm-send-strict.test.sh
|
|
373
|
+
tests/fm-spawn-batch.test.sh
|
|
374
|
+
tests/fm-supervision-instructions.test.sh
|
|
375
|
+
tests/fm-test-run.test.sh
|
|
376
|
+
tests/fm-tmux-submit-busy.test.sh
|
|
377
|
+
tests/fm-transition-lib.test.sh
|
|
378
|
+
tests/fm-x-mode.test.sh
|
|
379
|
+
EOF
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# Portable parallel shard 1: LPT balance of the proven-isolated set using the
|
|
383
|
+
# current concurrent-proof durations in docs/fm-test-isolation-proof.json.
|
|
384
|
+
# Execution order is longest first so wall-clock stays near the balanced sum.
|
|
385
|
+
list_portable_parallel_1() {
|
|
386
|
+
cat <<'EOF'
|
|
387
|
+
tests/fm-x-mode.test.sh
|
|
388
|
+
tests/fm-cd-pretool-check.test.sh
|
|
389
|
+
tests/fm-captain-hold-lifecycle.test.sh
|
|
390
|
+
tests/fm-test-run.test.sh
|
|
391
|
+
tests/fm-composer-ghost.test.sh
|
|
392
|
+
tests/fm-grok-harness.test.sh
|
|
393
|
+
tests/fm-lint.test.sh
|
|
394
|
+
tests/fm-pi-primary-types.test.sh
|
|
395
|
+
tests/fm-review-diff.test.sh
|
|
396
|
+
tests/fm-brief.test.sh
|
|
397
|
+
tests/fm-transition-lib.test.sh
|
|
398
|
+
EOF
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# Portable parallel shard 2: the complementary LPT half of the proven set.
|
|
402
|
+
list_portable_parallel_2() {
|
|
403
|
+
cat <<'EOF'
|
|
404
|
+
tests/fm-backend-herdr.test.sh
|
|
405
|
+
tests/fm-arm-pretool-check.test.sh
|
|
406
|
+
tests/fm-crew-state.test.sh
|
|
407
|
+
tests/fm-herdr-lab.test.sh
|
|
408
|
+
tests/fm-pr-merge.test.sh
|
|
409
|
+
tests/fm-send-popup-settle.test.sh
|
|
410
|
+
tests/fm-tmux-submit-busy.test.sh
|
|
411
|
+
tests/fm-send-settle.test.sh
|
|
412
|
+
tests/fm-send-strict.test.sh
|
|
413
|
+
tests/fm-spawn-batch.test.sh
|
|
414
|
+
tests/fm-supervision-instructions.test.sh
|
|
415
|
+
tests/fm-ensure-agents-md.test.sh
|
|
416
|
+
tests/fm-composer-lib.test.sh
|
|
417
|
+
EOF
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
# Families whose scripts are proven safe to run concurrently WITH EACH OTHER
|
|
421
|
+
# under the bounded local scheduler. Deliberately separate from the
|
|
422
|
+
# proven-isolated set, which must stay exactly equal to the portable CI shard
|
|
423
|
+
# union (see the coverage guard); these families keep their serial CI lane and
|
|
424
|
+
# only gain concurrency for a local run.
|
|
425
|
+
#
|
|
426
|
+
# Membership is empirical, never assumed:
|
|
427
|
+
# `bin/fm-test-isolation-proof.sh --pool <family> --jobs 4` is the owner of the
|
|
428
|
+
# proof, and docs/fm-test-isolation-proof.md records the dated result.
|
|
429
|
+
list_concurrent_safe_families() {
|
|
430
|
+
cat <<'EOF'
|
|
431
|
+
watcher-wake-lock
|
|
432
|
+
pure-contract-unit
|
|
433
|
+
EOF
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
family_is_concurrent_safe() {
|
|
437
|
+
local want=$1 line
|
|
438
|
+
while IFS= read -r line; do
|
|
439
|
+
[ "$line" = "$want" ] && return 0
|
|
440
|
+
done < <(list_concurrent_safe_families)
|
|
441
|
+
return 1
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
concurrent_safe_family_jobs_max() {
|
|
445
|
+
case "$1" in
|
|
446
|
+
watcher-wake-lock|pure-contract-unit) printf '4\n' ;;
|
|
447
|
+
*) printf '1\n' ;;
|
|
448
|
+
esac
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
# A script may run under --jobs when it is individually proven isolated or is
|
|
452
|
+
# an exact repository member of a family carrying a recorded concurrent proof.
|
|
453
|
+
script_allows_concurrency() {
|
|
454
|
+
local s=$1 family repo_script
|
|
455
|
+
is_proven_isolated_script "$s" && return 0
|
|
456
|
+
family=$(family_for_basename "$(basename "$s")")
|
|
457
|
+
family_is_concurrent_safe "$family" || return 1
|
|
458
|
+
while IFS= read -r repo_script; do
|
|
459
|
+
[ "$repo_script" = "$s" ] && return 0
|
|
460
|
+
done < <(all_repo_tests)
|
|
461
|
+
return 1
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
is_proven_isolated_script() {
|
|
465
|
+
local want=$1 line
|
|
466
|
+
while IFS= read -r line; do
|
|
467
|
+
[ "$line" = "$want" ] && return 0
|
|
468
|
+
done < <(list_proven_isolated)
|
|
469
|
+
return 1
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
# The portable serial remainder: every tests/*.test.sh that is neither
|
|
473
|
+
# proven-isolated nor real-herdr-gated. Watcher, lock, AFK, real tmux, daemon,
|
|
474
|
+
# secondmate lifecycle, bootstrap, live-harness opt-in, GUI-backend, and other
|
|
475
|
+
# unproven work stays here. Derived rather than enumerated so a newly added test
|
|
476
|
+
# lands here by default instead of falling out of every lane.
|
|
477
|
+
list_portable_serial() {
|
|
478
|
+
local s base fam
|
|
479
|
+
while IFS= read -r s; do
|
|
480
|
+
[ -n "$s" ] || continue
|
|
481
|
+
base=$(basename "$s")
|
|
482
|
+
fam=$(family_for_basename "$base")
|
|
483
|
+
if [ "$fam" = "real-herdr-gated" ]; then
|
|
484
|
+
continue
|
|
485
|
+
fi
|
|
486
|
+
if is_proven_isolated_script "$s"; then
|
|
487
|
+
continue
|
|
488
|
+
fi
|
|
489
|
+
printf '%s\n' "$s"
|
|
490
|
+
done < <(all_repo_tests)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
# Measured portable-serial script durations in milliseconds, from the CI timing
|
|
494
|
+
# artifact recorded in docs/fm-test-portable-shards.md. These are balance hints
|
|
495
|
+
# only: the shard partition stays complete and disjoint whatever they say, so a
|
|
496
|
+
# stale hint costs balance rather than coverage. That doc owns the refresh
|
|
497
|
+
# procedure.
|
|
498
|
+
portable_serial_weight_hints() {
|
|
499
|
+
cat <<'EOF'
|
|
500
|
+
tests/fm-afk-inject-e2e.test.sh 35900
|
|
501
|
+
tests/fm-afk-pi-herdr-return-e2e.test.sh 66
|
|
502
|
+
tests/fm-afk-return.test.sh 3974
|
|
503
|
+
tests/fm-ask-user-authority.test.sh 83
|
|
504
|
+
tests/fm-backend-cmux-smoke.test.sh 30
|
|
505
|
+
tests/fm-backend-cmux.test.sh 3351
|
|
506
|
+
tests/fm-backend-herdr-focus-flash-e2e.test.sh 21
|
|
507
|
+
tests/fm-backend-orca.test.sh 14681
|
|
508
|
+
tests/fm-backend-tmux-smoke.test.sh 361
|
|
509
|
+
tests/fm-backend-zellij-smoke.test.sh 22
|
|
510
|
+
tests/fm-backend-zellij.test.sh 8297
|
|
511
|
+
tests/fm-backend.test.sh 17169
|
|
512
|
+
tests/fm-backlog-handoff.test.sh 4157
|
|
513
|
+
tests/fm-bearings-board.test.sh 3385
|
|
514
|
+
tests/fm-bearings-snapshot.test.sh 68659
|
|
515
|
+
tests/fm-bootstrap-network-parallel.test.sh 8000
|
|
516
|
+
tests/fm-bootstrap.test.sh 38417
|
|
517
|
+
tests/fm-busy-adapter-wiring.test.sh 14880
|
|
518
|
+
tests/fm-busy-state.test.sh 714
|
|
519
|
+
tests/fm-calm-pi-extension.test.sh 464
|
|
520
|
+
tests/fm-classify-decision-key.test.sh 928
|
|
521
|
+
tests/fm-claude-stop-autoarm-live-e2e.test.sh 30
|
|
522
|
+
tests/fm-claude-stop-autoarm.test.sh 60633
|
|
523
|
+
tests/fm-cmux-claude-composer-live-e2e.test.sh 20
|
|
524
|
+
tests/fm-codex-continuity-live-e2e.test.sh 19
|
|
525
|
+
tests/fm-composer-matrix-live-e2e.test.sh 21
|
|
526
|
+
tests/fm-control-relaunch.test.sh 31881
|
|
527
|
+
tests/fm-control.test.sh 36712
|
|
528
|
+
tests/fm-cursor-harness.test.sh 30071
|
|
529
|
+
tests/fm-cursor-primary-live-e2e.test.sh 20
|
|
530
|
+
tests/fm-cursor-primary.test.sh 52324
|
|
531
|
+
tests/fm-daemon.test.sh 25834
|
|
532
|
+
tests/fm-documentation-audiences.test.sh 642
|
|
533
|
+
tests/fm-fleet-snapshot-view.test.sh 6995
|
|
534
|
+
tests/fm-fleet-sync.test.sh 20194
|
|
535
|
+
tests/fm-extension-binding.test.sh 35000
|
|
536
|
+
tests/fm-gate-refuse.test.sh 4071
|
|
537
|
+
tests/fm-gitignore-config.test.sh 63
|
|
538
|
+
tests/fm-gotmp.test.sh 762
|
|
539
|
+
tests/fm-grok-continuity-live-e2e.test.sh 19
|
|
540
|
+
tests/fm-grok-stop-live-e2e.test.sh 21
|
|
541
|
+
tests/fm-harness-adapter-instructions-live-e2e.test.sh 20
|
|
542
|
+
tests/fm-harness-adapter-references.test.sh 2
|
|
543
|
+
tests/fm-guard-stale-banner.test.sh 11280
|
|
544
|
+
tests/fm-harness-liveness-drift-live-e2e.test.sh 19
|
|
545
|
+
tests/fm-herdr-session-cleanup.test.sh 14120
|
|
546
|
+
tests/fm-herdr-submit-confirm-live-e2e.test.sh 20
|
|
547
|
+
tests/fm-herdr-version-floor-live-e2e.test.sh 20
|
|
548
|
+
tests/fm-inactive-reconcile.test.sh 41671
|
|
549
|
+
tests/fm-kimi-harness.test.sh 15092
|
|
550
|
+
tests/fm-lint-workflows.test.sh 744
|
|
551
|
+
tests/fm-muse-harness.test.sh 27414
|
|
552
|
+
tests/fm-muse-signals-live-e2e.test.sh 21
|
|
553
|
+
tests/fm-on.test.sh 8602
|
|
554
|
+
tests/fm-opencode-primary-live-e2e.test.sh 22
|
|
555
|
+
tests/fm-operational-input.test.sh 246
|
|
556
|
+
tests/fm-peek-remote.test.sh 848
|
|
557
|
+
tests/fm-pending-reply.test.sh 19488
|
|
558
|
+
tests/fm-pi-primary-live-e2e.test.sh 41
|
|
559
|
+
tests/fm-pi-watch-extension.test.sh 17979
|
|
560
|
+
tests/fm-pr-check-security.test.sh 250417
|
|
561
|
+
tests/fm-procevent-when.test.sh 15249
|
|
562
|
+
tests/fm-procevent.test.sh 53142
|
|
563
|
+
tests/fm-project-origin.test.sh 105
|
|
564
|
+
tests/fm-public-followup.test.sh 36301
|
|
565
|
+
tests/fm-quota-array-dispatch-live-e2e.test.sh 18
|
|
566
|
+
tests/fm-remote-backlog-handoff.test.sh 20389
|
|
567
|
+
tests/fm-remote-doctor.test.sh 4705
|
|
568
|
+
tests/fm-remote-entrypoint.test.sh 98
|
|
569
|
+
tests/fm-remote-job-orphan-reap.test.sh 2903
|
|
570
|
+
tests/fm-remote-job.test.sh 48068
|
|
571
|
+
tests/fm-remote-reply.test.sh 40906
|
|
572
|
+
tests/fm-remote-secondmate-lifecycle-e2e.test.sh 170240
|
|
573
|
+
tests/fm-remote-secondmate-parent-binding.test.sh 13064
|
|
574
|
+
tests/fm-remote-secondmate-trace-context.test.sh 39927
|
|
575
|
+
tests/fm-secondmate-harness.test.sh 123471
|
|
576
|
+
tests/fm-secondmate-lifecycle-e2e.test.sh 6539
|
|
577
|
+
tests/fm-secondmate-liveness.test.sh 16365
|
|
578
|
+
tests/fm-secondmate-safety.test.sh 49011
|
|
579
|
+
tests/fm-secondmate-sync.test.sh 29236
|
|
580
|
+
tests/fm-send-remote-delivery.test.sh 4892
|
|
581
|
+
tests/fm-send-resolve-key.test.sh 13450
|
|
582
|
+
tests/fm-send-secondmate-marker-herdr-e2e.test.sh 45
|
|
583
|
+
tests/fm-send-secondmate-marker.test.sh 4439
|
|
584
|
+
tests/fm-session-lock-ancestry.test.sh 1205
|
|
585
|
+
tests/fm-session-start.test.sh 144836
|
|
586
|
+
tests/fm-sessionstart-hook-live-e2e.test.sh 21
|
|
587
|
+
tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh 21
|
|
588
|
+
tests/fm-sessionstart-nudge.test.sh 26684
|
|
589
|
+
tests/fm-shared-captain-inheritance.test.sh 10672
|
|
590
|
+
tests/fm-spawn-dispatch-profile.test.sh 57765
|
|
591
|
+
tests/fm-spawn-pool-base-freshen.test.sh 13257
|
|
592
|
+
tests/fm-spawn-worktree-settle.test.sh 4828
|
|
593
|
+
tests/fm-startup-memory-budget.test.sh 6550
|
|
594
|
+
tests/fm-startup-network.test.sh 48888
|
|
595
|
+
tests/fm-stow-cascade.test.sh 2986
|
|
596
|
+
tests/fm-subagent-pretool-check.test.sh 1066
|
|
597
|
+
tests/fm-supervision-events.test.sh 1431
|
|
598
|
+
tests/fm-tangle-guard.test.sh 8364
|
|
599
|
+
tests/fm-task-delivery.test.sh 2414
|
|
600
|
+
tests/fm-teardown-endpoint-safety.test.sh 7295
|
|
601
|
+
tests/fm-teardown.test.sh 87400
|
|
602
|
+
tests/fm-test-fixture-cleanup.test.sh 532
|
|
603
|
+
tests/fm-test-fixtures.test.sh 1045
|
|
604
|
+
tests/fm-test-isolation-proof.test.sh 451
|
|
605
|
+
tests/fm-tmux-agent-liveness.test.sh 4065
|
|
606
|
+
tests/fm-tool-update-check.test.sh 12846
|
|
607
|
+
tests/fm-trace-context-lib.test.sh 194
|
|
608
|
+
tests/fm-trace-context-spawn.test.sh 35325
|
|
609
|
+
tests/fm-turnend-guard.test.sh 34915
|
|
610
|
+
tests/fm-update.test.sh 5280
|
|
611
|
+
tests/fm-vendor-auth-probe.test.sh 43243
|
|
612
|
+
tests/fm-wake-daemon-lifecycle-e2e.test.sh 6219
|
|
613
|
+
tests/fm-wake-drain-open-decisions-cursor.test.sh 17357
|
|
614
|
+
tests/fm-wake-drain-open-decisions.test.sh 11300
|
|
615
|
+
tests/fm-wake-drain-unread-status.test.sh 25214
|
|
616
|
+
tests/fm-wake-queue.test.sh 30887
|
|
617
|
+
tests/fm-watch-arm.test.sh 53598
|
|
618
|
+
tests/fm-watch-checkpoint.test.sh 5293
|
|
619
|
+
tests/fm-watch-recovery-loop.test.sh 58721
|
|
620
|
+
tests/fm-watch-triage.test.sh 142409
|
|
621
|
+
tests/fm-watcher-lock.test.sh 54364
|
|
622
|
+
EOF
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
portable_serial_weight_for() {
|
|
626
|
+
local want=$1 path ms
|
|
627
|
+
while read -r path ms; do
|
|
628
|
+
if [ "$path" = "$want" ]; then
|
|
629
|
+
printf '%s\n' "$ms"
|
|
630
|
+
return 0
|
|
631
|
+
fi
|
|
632
|
+
done < <(portable_serial_weight_hints)
|
|
633
|
+
printf '%s\n' "$PORTABLE_SERIAL_DEFAULT_WEIGHT_MS"
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
# Longest-processing-time assignment of the serial remainder to
|
|
637
|
+
# PORTABLE_SERIAL_SHARDS bins, printing "<shard>\t<script>" for every script.
|
|
638
|
+
# Deterministic: candidates are ordered by hint descending then path, and ties
|
|
639
|
+
# between equally loaded bins always take the lowest bin index.
|
|
640
|
+
portable_serial_assignments() {
|
|
641
|
+
local ms script i best best_load
|
|
642
|
+
local -a loads=()
|
|
643
|
+
i=1
|
|
644
|
+
while [ "$i" -le "$PORTABLE_SERIAL_SHARDS" ]; do
|
|
645
|
+
loads[i]=0
|
|
646
|
+
i=$((i + 1))
|
|
647
|
+
done
|
|
648
|
+
while IFS=$'\t' read -r ms script; do
|
|
649
|
+
[ -n "$script" ] || continue
|
|
650
|
+
best=1
|
|
651
|
+
best_load=${loads[1]}
|
|
652
|
+
i=2
|
|
653
|
+
while [ "$i" -le "$PORTABLE_SERIAL_SHARDS" ]; do
|
|
654
|
+
if [ "${loads[i]}" -lt "$best_load" ]; then
|
|
655
|
+
best_load=${loads[i]}
|
|
656
|
+
best=$i
|
|
657
|
+
fi
|
|
658
|
+
i=$((i + 1))
|
|
659
|
+
done
|
|
660
|
+
loads[best]=$((best_load + ms))
|
|
661
|
+
printf '%s\t%s\n' "$best" "$script"
|
|
662
|
+
done < <(
|
|
663
|
+
while IFS= read -r script; do
|
|
664
|
+
[ -n "$script" ] || continue
|
|
665
|
+
printf '%s\t%s\n' "$(portable_serial_weight_for "$script")" "$script"
|
|
666
|
+
done < <(list_portable_serial) | LC_ALL=C sort -t$'\t' -k1,1nr -k2,2
|
|
667
|
+
)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
# Parse "<k>of<n>" from a portable-serial shard lane and echo <k>, refusing when
|
|
671
|
+
# <n> disagrees with this script's configured count so a CI matrix built for a
|
|
672
|
+
# different shard count fails loudly instead of dropping tests.
|
|
673
|
+
portable_serial_shard_index() {
|
|
674
|
+
local lane=$1 spec index count
|
|
675
|
+
spec=${lane#portable-serial-}
|
|
676
|
+
index=${spec%%of*}
|
|
677
|
+
count=${spec#*of}
|
|
678
|
+
case "$spec" in
|
|
679
|
+
*of*) ;;
|
|
680
|
+
*) die "unknown lane '$lane' (see --list-lanes)" ;;
|
|
681
|
+
esac
|
|
682
|
+
case "$index" in
|
|
683
|
+
''|*[!0-9]*) die "unknown lane '$lane' (see --list-lanes)" ;;
|
|
684
|
+
esac
|
|
685
|
+
case "$count" in
|
|
686
|
+
''|*[!0-9]*) die "unknown lane '$lane' (see --list-lanes)" ;;
|
|
687
|
+
esac
|
|
688
|
+
if [ "$count" -ne "$PORTABLE_SERIAL_SHARDS" ]; then
|
|
689
|
+
die "lane '$lane' asks for $count portable serial shards but this runner is configured for $PORTABLE_SERIAL_SHARDS (see --list-lanes)"
|
|
690
|
+
fi
|
|
691
|
+
if [ "$index" -lt 1 ] || [ "$index" -gt "$PORTABLE_SERIAL_SHARDS" ]; then
|
|
692
|
+
die "lane '$lane' shard index is outside 1..$PORTABLE_SERIAL_SHARDS (see --list-lanes)"
|
|
693
|
+
fi
|
|
694
|
+
printf '%s\n' "$index"
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
select_proven_isolated() {
|
|
698
|
+
local s
|
|
699
|
+
while IFS= read -r s; do
|
|
700
|
+
[ -n "$s" ] || continue
|
|
701
|
+
add_script "$s"
|
|
702
|
+
done < <(list_proven_isolated)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
select_lane() {
|
|
706
|
+
local want=$1 s shard idx found=0
|
|
707
|
+
case "$want" in
|
|
708
|
+
portable-parallel-1)
|
|
709
|
+
while IFS= read -r s; do
|
|
710
|
+
[ -n "$s" ] || continue
|
|
711
|
+
add_script "$s"
|
|
712
|
+
found=1
|
|
713
|
+
done < <(list_portable_parallel_1)
|
|
714
|
+
;;
|
|
715
|
+
portable-parallel-2)
|
|
716
|
+
while IFS= read -r s; do
|
|
717
|
+
[ -n "$s" ] || continue
|
|
718
|
+
add_script "$s"
|
|
719
|
+
found=1
|
|
720
|
+
done < <(list_portable_parallel_2)
|
|
721
|
+
;;
|
|
722
|
+
portable-serial)
|
|
723
|
+
while IFS= read -r s; do
|
|
724
|
+
[ -n "$s" ] || continue
|
|
725
|
+
add_script "$s"
|
|
726
|
+
found=1
|
|
727
|
+
done < <(list_portable_serial)
|
|
728
|
+
;;
|
|
729
|
+
portable-serial-*)
|
|
730
|
+
# One separate-runner shard of the same remainder, still serial in itself.
|
|
731
|
+
shard=$(portable_serial_shard_index "$want")
|
|
732
|
+
while IFS=$'\t' read -r idx s; do
|
|
733
|
+
[ -n "$s" ] || continue
|
|
734
|
+
if [ "$idx" = "$shard" ]; then
|
|
735
|
+
add_script "$s"
|
|
736
|
+
found=1
|
|
737
|
+
fi
|
|
738
|
+
done < <(portable_serial_assignments)
|
|
739
|
+
;;
|
|
740
|
+
real-herdr-gated)
|
|
741
|
+
select_family real-herdr-gated
|
|
742
|
+
found=1
|
|
743
|
+
;;
|
|
744
|
+
*)
|
|
745
|
+
die "unknown lane '$want' (see --list-lanes)"
|
|
746
|
+
;;
|
|
747
|
+
esac
|
|
748
|
+
[ "$found" -eq 1 ] || die "lane '$want' selected no tests"
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
run_coverage_guard() {
|
|
752
|
+
local tmp missing extra a b shard
|
|
753
|
+
local -a saved_scripts=()
|
|
754
|
+
tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-coverage.XXXXXX")
|
|
755
|
+
|
|
756
|
+
all_repo_tests | LC_ALL=C sort -u >"$tmp/all"
|
|
757
|
+
list_proven_isolated | LC_ALL=C sort -u >"$tmp/proven"
|
|
758
|
+
list_portable_parallel_1 | LC_ALL=C sort -u >"$tmp/s1"
|
|
759
|
+
list_portable_parallel_2 | LC_ALL=C sort -u >"$tmp/s2"
|
|
760
|
+
|
|
761
|
+
cat "$tmp/s1" "$tmp/s2" | LC_ALL=C sort | uniq -d >"$tmp/shard_dups"
|
|
762
|
+
if [ -s "$tmp/shard_dups" ]; then
|
|
763
|
+
log "coverage guard: portable parallel shards share scripts:"
|
|
764
|
+
cat "$tmp/shard_dups" >&2
|
|
765
|
+
rm -rf "$tmp"
|
|
766
|
+
return 1
|
|
767
|
+
fi
|
|
768
|
+
cat "$tmp/s1" "$tmp/s2" | LC_ALL=C sort -u >"$tmp/shards_union"
|
|
769
|
+
missing=$(comm -23 "$tmp/proven" "$tmp/shards_union" || true)
|
|
770
|
+
extra=$(comm -13 "$tmp/proven" "$tmp/shards_union" || true)
|
|
771
|
+
if [ -n "$missing" ] || [ -n "$extra" ]; then
|
|
772
|
+
log "coverage guard: portable shards must equal the proven-isolated set"
|
|
773
|
+
[ -z "$missing" ] || { log "missing from shards:"; printf '%s\n' "$missing" >&2; }
|
|
774
|
+
[ -z "$extra" ] || { log "extra beyond proven:"; printf '%s\n' "$extra" >&2; }
|
|
775
|
+
rm -rf "$tmp"
|
|
776
|
+
return 1
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
# Serial (whole lane and each CI shard) + Herdr lane listings without
|
|
780
|
+
# disturbing a caller's selection.
|
|
781
|
+
saved_scripts=("${SCRIPTS[@]+"${SCRIPTS[@]}"}")
|
|
782
|
+
SCRIPTS=()
|
|
783
|
+
select_lane portable-serial
|
|
784
|
+
printf '%s\n' "${SCRIPTS[@]+"${SCRIPTS[@]}"}" | LC_ALL=C sort -u >"$tmp/serial"
|
|
785
|
+
: >"$tmp/serial_shards_raw"
|
|
786
|
+
shard=1
|
|
787
|
+
while [ "$shard" -le "$PORTABLE_SERIAL_SHARDS" ]; do
|
|
788
|
+
SCRIPTS=()
|
|
789
|
+
select_lane "portable-serial-${shard}of${PORTABLE_SERIAL_SHARDS}"
|
|
790
|
+
if [ "${#SCRIPTS[@]}" -eq 0 ]; then
|
|
791
|
+
log "coverage guard: portable serial shard $shard of $PORTABLE_SERIAL_SHARDS is empty"
|
|
792
|
+
SCRIPTS=("${saved_scripts[@]+"${saved_scripts[@]}"}")
|
|
793
|
+
rm -rf "$tmp"
|
|
794
|
+
return 1
|
|
795
|
+
fi
|
|
796
|
+
printf '%s\n' "${SCRIPTS[@]+"${SCRIPTS[@]}"}" >>"$tmp/serial_shards_raw"
|
|
797
|
+
shard=$((shard + 1))
|
|
798
|
+
done
|
|
799
|
+
SCRIPTS=()
|
|
800
|
+
select_family real-herdr-gated
|
|
801
|
+
printf '%s\n' "${SCRIPTS[@]+"${SCRIPTS[@]}"}" | LC_ALL=C sort -u >"$tmp/herdr"
|
|
802
|
+
SCRIPTS=("${saved_scripts[@]+"${saved_scripts[@]}"}")
|
|
803
|
+
|
|
804
|
+
# Every serial script runs in exactly one CI shard: no duplicate work across
|
|
805
|
+
# runners, and no script silently left out of the required lane.
|
|
806
|
+
LC_ALL=C sort "$tmp/serial_shards_raw" | uniq -d >"$tmp/serial_shard_dups"
|
|
807
|
+
if [ -s "$tmp/serial_shard_dups" ]; then
|
|
808
|
+
log "coverage guard: portable serial shards share scripts:"
|
|
809
|
+
cat "$tmp/serial_shard_dups" >&2
|
|
810
|
+
rm -rf "$tmp"
|
|
811
|
+
return 1
|
|
812
|
+
fi
|
|
813
|
+
LC_ALL=C sort -u "$tmp/serial_shards_raw" >"$tmp/serial_shards"
|
|
814
|
+
missing=$(comm -23 "$tmp/serial" "$tmp/serial_shards" || true)
|
|
815
|
+
extra=$(comm -13 "$tmp/serial" "$tmp/serial_shards" || true)
|
|
816
|
+
if [ -n "$missing" ] || [ -n "$extra" ]; then
|
|
817
|
+
log "coverage guard: portable serial shards must equal the portable serial lane"
|
|
818
|
+
[ -z "$missing" ] || { log "missing from serial shards:"; printf '%s\n' "$missing" >&2; }
|
|
819
|
+
[ -z "$extra" ] || { log "extra beyond serial lane:"; printf '%s\n' "$extra" >&2; }
|
|
820
|
+
rm -rf "$tmp"
|
|
821
|
+
return 1
|
|
822
|
+
fi
|
|
823
|
+
|
|
824
|
+
for pair in "shards_union:serial" "shards_union:herdr" "serial:herdr"; do
|
|
825
|
+
a=${pair%%:*}
|
|
826
|
+
b=${pair#*:}
|
|
827
|
+
comm -12 "$tmp/$a" "$tmp/$b" >"$tmp/overlap"
|
|
828
|
+
if [ -s "$tmp/overlap" ]; then
|
|
829
|
+
log "coverage guard: overlap between $a and $b:"
|
|
830
|
+
cat "$tmp/overlap" >&2
|
|
831
|
+
rm -rf "$tmp"
|
|
832
|
+
return 1
|
|
833
|
+
fi
|
|
834
|
+
done
|
|
835
|
+
|
|
836
|
+
cat "$tmp/shards_union" "$tmp/serial" "$tmp/herdr" | LC_ALL=C sort >"$tmp/union_raw"
|
|
837
|
+
uniq -d "$tmp/union_raw" >"$tmp/union_dups"
|
|
838
|
+
if [ -s "$tmp/union_dups" ]; then
|
|
839
|
+
log "coverage guard: duplicate scripts across lanes:"
|
|
840
|
+
cat "$tmp/union_dups" >&2
|
|
841
|
+
rm -rf "$tmp"
|
|
842
|
+
return 1
|
|
843
|
+
fi
|
|
844
|
+
LC_ALL=C sort -u "$tmp/union_raw" >"$tmp/union"
|
|
845
|
+
missing=$(comm -23 "$tmp/all" "$tmp/union" || true)
|
|
846
|
+
extra=$(comm -13 "$tmp/all" "$tmp/union" || true)
|
|
847
|
+
if [ -n "$missing" ] || [ -n "$extra" ]; then
|
|
848
|
+
log "coverage guard: union of portable shards + portable serial + Herdr must equal tests/*.test.sh"
|
|
849
|
+
[ -z "$missing" ] || { log "missing from union:"; printf '%s\n' "$missing" >&2; }
|
|
850
|
+
[ -z "$extra" ] || { log "extra beyond inventory:"; printf '%s\n' "$extra" >&2; }
|
|
851
|
+
rm -rf "$tmp"
|
|
852
|
+
return 1
|
|
853
|
+
fi
|
|
854
|
+
|
|
855
|
+
if [ -x "$ROOT/bin/fm-test-isolation-proof.sh" ]; then
|
|
856
|
+
"$ROOT/bin/fm-test-isolation-proof.sh" --list | LC_ALL=C sort -u >"$tmp/proof_list"
|
|
857
|
+
if ! cmp -s "$tmp/proven" "$tmp/proof_list"; then
|
|
858
|
+
log "coverage guard: embedded proven-isolated set diverges from bin/fm-test-isolation-proof.sh --list"
|
|
859
|
+
comm -3 "$tmp/proven" "$tmp/proof_list" >&2 || true
|
|
860
|
+
rm -rf "$tmp"
|
|
861
|
+
return 1
|
|
862
|
+
fi
|
|
863
|
+
fi
|
|
864
|
+
|
|
865
|
+
printf 'FM_TEST_COVERAGE ok total=%s parallel=%s serial=%s serial_shards=%s herdr=%s\n' \
|
|
866
|
+
"$(wc -l <"$tmp/all" | tr -d ' ')" \
|
|
867
|
+
"$(wc -l <"$tmp/shards_union" | tr -d ' ')" \
|
|
868
|
+
"$(wc -l <"$tmp/serial" | tr -d ' ')" \
|
|
869
|
+
"$PORTABLE_SERIAL_SHARDS" \
|
|
870
|
+
"$(wc -l <"$tmp/herdr" | tr -d ' ')"
|
|
871
|
+
rm -rf "$tmp"
|
|
872
|
+
return 0
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
aggregate_timing_json() {
|
|
876
|
+
local out=$1
|
|
877
|
+
shift
|
|
878
|
+
[ "$#" -gt 0 ] || die "--aggregate-json requires at least one input timing JSON"
|
|
879
|
+
command -v python3 >/dev/null 2>&1 || die "--aggregate-json requires python3"
|
|
880
|
+
python3 - "$out" "$@" <<'PY'
|
|
881
|
+
import json, sys
|
|
882
|
+
from pathlib import Path
|
|
883
|
+
|
|
884
|
+
out = Path(sys.argv[1])
|
|
885
|
+
inputs = [Path(p) for p in sys.argv[2:]]
|
|
886
|
+
lanes = []
|
|
887
|
+
all_scripts = []
|
|
888
|
+
failed = 0
|
|
889
|
+
skipped = 0
|
|
890
|
+
total = 0
|
|
891
|
+
wall_ms = 0
|
|
892
|
+
for path in inputs:
|
|
893
|
+
doc = json.loads(path.read_text(encoding="utf-8"))
|
|
894
|
+
summary = doc.get("summary") or {}
|
|
895
|
+
lane = {
|
|
896
|
+
"path": str(path),
|
|
897
|
+
"run_id": doc.get("run_id"),
|
|
898
|
+
"selection": doc.get("selection"),
|
|
899
|
+
"started_at": doc.get("started_at"),
|
|
900
|
+
"finished_at": doc.get("finished_at"),
|
|
901
|
+
"summary": summary,
|
|
902
|
+
}
|
|
903
|
+
lanes.append(lane)
|
|
904
|
+
total += int(summary.get("total") or 0)
|
|
905
|
+
failed += int(summary.get("failed") or 0)
|
|
906
|
+
skipped += int(summary.get("skipped_gate") or 0)
|
|
907
|
+
wall_ms = max(wall_ms, int(summary.get("duration_ms") or 0))
|
|
908
|
+
for s in doc.get("scripts") or []:
|
|
909
|
+
row = dict(s)
|
|
910
|
+
row["lane_selection"] = doc.get("selection")
|
|
911
|
+
row["lane_run_id"] = doc.get("run_id")
|
|
912
|
+
all_scripts.append(row)
|
|
913
|
+
|
|
914
|
+
all_scripts.sort(key=lambda s: (-int(s.get("duration_ms") or 0), s.get("path") or ""))
|
|
915
|
+
agg = {
|
|
916
|
+
"kind": "aggregate",
|
|
917
|
+
"lanes": lanes,
|
|
918
|
+
"summary": {
|
|
919
|
+
"lanes": len(lanes),
|
|
920
|
+
"total": total,
|
|
921
|
+
"failed": failed,
|
|
922
|
+
"skipped_gate": skipped,
|
|
923
|
+
"critical_path_duration_ms": wall_ms,
|
|
924
|
+
},
|
|
925
|
+
"scripts": all_scripts,
|
|
926
|
+
"slowest": all_scripts[:15],
|
|
927
|
+
}
|
|
928
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
929
|
+
out.write_text(json.dumps(agg, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
|
930
|
+
print(f"FM_TEST_AGGREGATE lanes={len(lanes)} total={total} failed={failed} skipped_gate={skipped} critical_path_duration_ms={wall_ms}")
|
|
931
|
+
PY
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
all_repo_tests() {
|
|
935
|
+
# Deterministic lexical order (same as bash glob expansion under LC_ALL=C).
|
|
936
|
+
local f
|
|
937
|
+
# shellcheck disable=SC2035
|
|
938
|
+
for f in tests/*.test.sh; do
|
|
939
|
+
[ -f "$f" ] || continue
|
|
940
|
+
printf '%s\n' "$f"
|
|
941
|
+
done | LC_ALL=C sort
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
normalize_script_path() {
|
|
945
|
+
local p=$1
|
|
946
|
+
case "$p" in
|
|
947
|
+
/*) printf '%s\n' "$p" ;;
|
|
948
|
+
tests/*|./tests/*)
|
|
949
|
+
p=${p#./}
|
|
950
|
+
printf '%s\n' "$p"
|
|
951
|
+
;;
|
|
952
|
+
*.test.sh)
|
|
953
|
+
if [ -f "tests/$p" ]; then
|
|
954
|
+
printf 'tests/%s\n' "$p"
|
|
955
|
+
else
|
|
956
|
+
printf '%s\n' "$p"
|
|
957
|
+
fi
|
|
958
|
+
;;
|
|
959
|
+
*)
|
|
960
|
+
printf '%s\n' "$p"
|
|
961
|
+
;;
|
|
962
|
+
esac
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
# Append unique relative-or-absolute script paths to SCRIPTS.
|
|
966
|
+
add_script() {
|
|
967
|
+
local p existing
|
|
968
|
+
p=$(normalize_script_path "$1")
|
|
969
|
+
for existing in "${SCRIPTS[@]+"${SCRIPTS[@]}"}"; do
|
|
970
|
+
[ "$existing" = "$p" ] && return 0
|
|
971
|
+
done
|
|
972
|
+
SCRIPTS+=("$p")
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
select_all() {
|
|
976
|
+
local s
|
|
977
|
+
while IFS= read -r s; do
|
|
978
|
+
[ -n "$s" ] || continue
|
|
979
|
+
add_script "$s"
|
|
980
|
+
done < <(all_repo_tests)
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
select_family() {
|
|
984
|
+
local want=$1 s base fam found=0
|
|
985
|
+
[ -n "$want" ] || die "--family requires a name"
|
|
986
|
+
while IFS= read -r s; do
|
|
987
|
+
[ -n "$s" ] || continue
|
|
988
|
+
base=$(basename "$s")
|
|
989
|
+
fam=$(family_for_basename "$base")
|
|
990
|
+
if [ "$fam" = "$want" ]; then
|
|
991
|
+
add_script "$s"
|
|
992
|
+
found=1
|
|
993
|
+
fi
|
|
994
|
+
done < <(all_repo_tests)
|
|
995
|
+
[ "$found" -eq 1 ] || die "no tests mapped to family '$want'"
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
families_for_test_reference() {
|
|
999
|
+
local needle=$1 s
|
|
1000
|
+
local found=0
|
|
1001
|
+
while IFS= read -r s; do
|
|
1002
|
+
[ -n "$s" ] || continue
|
|
1003
|
+
if grep -Fq "$needle" "$s"; then
|
|
1004
|
+
family_for_basename "$(basename "$s")"
|
|
1005
|
+
found=1
|
|
1006
|
+
fi
|
|
1007
|
+
done < <(all_repo_tests)
|
|
1008
|
+
[ "$found" -eq 1 ]
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
# Tests that name <needle>, selected as individual scripts rather than widened
|
|
1012
|
+
# to each referencing test's whole family. A direct reference is per-script
|
|
1013
|
+
# evidence, so it selects per script: one real-Herdr E2E sourcing a shared
|
|
1014
|
+
# helper must not drag in every other script of that expensive family.
|
|
1015
|
+
scripts_for_test_reference() {
|
|
1016
|
+
local needle=$1 s
|
|
1017
|
+
local found=0
|
|
1018
|
+
while IFS= read -r s; do
|
|
1019
|
+
[ -n "$s" ] || continue
|
|
1020
|
+
if grep -Fq "$needle" "$s"; then
|
|
1021
|
+
printf '__script__:%s\n' "$(basename "$s")"
|
|
1022
|
+
found=1
|
|
1023
|
+
fi
|
|
1024
|
+
done < <(all_repo_tests)
|
|
1025
|
+
[ "$found" -eq 1 ]
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
# bin/ scripts other than <needle> itself that name <needle>.
|
|
1029
|
+
bin_consumers_of() {
|
|
1030
|
+
local needle=$1 b
|
|
1031
|
+
for b in bin/*.sh bin/backends/*.sh; do
|
|
1032
|
+
[ -f "$b" ] || continue
|
|
1033
|
+
[ "$(basename "$b")" = "$needle" ] || ! grep -Fq "$needle" "$b" || printf '%s\n' "$b"
|
|
1034
|
+
done
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
# An unmapped bin/ path has no curated family of its own. Its blast radius is
|
|
1038
|
+
# the tests that name it, plus the curated families of the bin/ scripts that
|
|
1039
|
+
# consume it. Direct test references resolve per script (above) while consumer
|
|
1040
|
+
# scripts resolve back through the curated map, so genuine family-level
|
|
1041
|
+
# coupling a maintainer recorded is preserved while an incidental single-script
|
|
1042
|
+
# reference no longer selects that script's whole family.
|
|
1043
|
+
BIN_FALLBACK_DEPTH=0
|
|
1044
|
+
families_for_unmapped_bin() {
|
|
1045
|
+
local path=$1 needle consumer out found=0
|
|
1046
|
+
needle=$(basename "$path")
|
|
1047
|
+
if out=$(scripts_for_test_reference "$needle"); then
|
|
1048
|
+
printf '%s\n' "$out"
|
|
1049
|
+
found=1
|
|
1050
|
+
fi
|
|
1051
|
+
if [ "$BIN_FALLBACK_DEPTH" -lt 2 ]; then
|
|
1052
|
+
BIN_FALLBACK_DEPTH=$((BIN_FALLBACK_DEPTH + 1))
|
|
1053
|
+
while IFS= read -r consumer; do
|
|
1054
|
+
[ -n "$consumer" ] || continue
|
|
1055
|
+
out=$(families_for_changed_path "$consumer" | grep -v '^__unmapped__:' || true)
|
|
1056
|
+
if [ -n "$out" ]; then
|
|
1057
|
+
printf '%s\n' "$out"
|
|
1058
|
+
found=1
|
|
1059
|
+
fi
|
|
1060
|
+
done < <(bin_consumers_of "$needle")
|
|
1061
|
+
BIN_FALLBACK_DEPTH=$((BIN_FALLBACK_DEPTH - 1))
|
|
1062
|
+
fi
|
|
1063
|
+
[ "$found" -eq 1 ]
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
# Conservative path → family map. Over-selects rather than under-selects.
|
|
1067
|
+
# Never expands to the complete suite.
|
|
1068
|
+
families_for_changed_path() {
|
|
1069
|
+
local path=$1 fixture_ref
|
|
1070
|
+
case "$path" in
|
|
1071
|
+
tests/fm-backend-herdr-eventwait.test.py)
|
|
1072
|
+
printf '%s\n' real-herdr-gated
|
|
1073
|
+
printf '%s\n' backend-dispatch
|
|
1074
|
+
;;
|
|
1075
|
+
tests/*.test.sh)
|
|
1076
|
+
# A single test file change selects only that script via basename family
|
|
1077
|
+
# resolution in the caller; emit a marker family of __script__
|
|
1078
|
+
printf '%s\n' "__script__:$(basename "$path")"
|
|
1079
|
+
;;
|
|
1080
|
+
bin/fm-test-run.sh|bin/fm-test-isolation-proof.sh)
|
|
1081
|
+
# Deliberately the WHOLE family, not just the two contract tests. This
|
|
1082
|
+
# runner executes every pure-contract-unit script, so a change to it is
|
|
1083
|
+
# only proven by running them: its own contract test passing says the
|
|
1084
|
+
# runner's logic is right, not that the suite it drives still runs.
|
|
1085
|
+
printf '%s\n' pure-contract-unit
|
|
1086
|
+
;;
|
|
1087
|
+
bin/backends/herdr*|bin/fm-herdr-lab.sh|tests/herdr-test-safety.sh)
|
|
1088
|
+
printf '%s\n' real-herdr-gated
|
|
1089
|
+
printf '%s\n' backend-dispatch
|
|
1090
|
+
printf '%s\n' pure-contract-unit
|
|
1091
|
+
;;
|
|
1092
|
+
bin/fm-herdr-session-cleanup.sh)
|
|
1093
|
+
printf '%s\n' session-bootstrap
|
|
1094
|
+
printf '%s\n' real-herdr-gated
|
|
1095
|
+
printf '%s\n' backend-dispatch
|
|
1096
|
+
;;
|
|
1097
|
+
bin/backends/zellij*|tests/zellij-test-safety.sh)
|
|
1098
|
+
printf '%s\n' zellij
|
|
1099
|
+
printf '%s\n' backend-dispatch
|
|
1100
|
+
;;
|
|
1101
|
+
bin/backends/cmux*|tests/cmux-test-safety.sh)
|
|
1102
|
+
printf '%s\n' cmux
|
|
1103
|
+
printf '%s\n' backend-dispatch
|
|
1104
|
+
;;
|
|
1105
|
+
bin/backends/orca*|bin/backends/tmux.sh)
|
|
1106
|
+
printf '%s\n' backend-dispatch
|
|
1107
|
+
printf '%s\n' orca
|
|
1108
|
+
;;
|
|
1109
|
+
bin/fm-backend.sh|bin/fm-backend-hometag-lib.sh)
|
|
1110
|
+
printf '%s\n' backend-dispatch
|
|
1111
|
+
printf '%s\n' real-herdr-gated
|
|
1112
|
+
;;
|
|
1113
|
+
bin/fm-watch*|bin/fm-wake*|bin/fm-inactive-reconcile.sh|\
|
|
1114
|
+
bin/fm-classify-lib.sh|bin/fm-daemon*|bin/fm-turnend-guard*|bin/fm-guard.sh)
|
|
1115
|
+
printf '%s\n' watcher-wake-lock
|
|
1116
|
+
;;
|
|
1117
|
+
bin/fm-afk*)
|
|
1118
|
+
printf '%s\n' afk
|
|
1119
|
+
printf '%s\n' real-herdr-gated
|
|
1120
|
+
;;
|
|
1121
|
+
bin/fm-supervisor-target-lib.sh)
|
|
1122
|
+
printf '%s\n' watcher-wake-lock
|
|
1123
|
+
printf '%s\n' real-herdr-gated
|
|
1124
|
+
printf '%s\n' live-harness-optin
|
|
1125
|
+
printf '%s\n' afk
|
|
1126
|
+
;;
|
|
1127
|
+
bin/fm-startup-memory-budget.sh|bin/fm-startup-memory-budget-lib.sh)
|
|
1128
|
+
printf '%s\n' secondmate
|
|
1129
|
+
printf '%s\n' session-bootstrap
|
|
1130
|
+
;;
|
|
1131
|
+
bin/fm-secondmate*|bin/fm-remote*|bin/fm-on.sh|bin/fm-home-seed.sh|\
|
|
1132
|
+
bin/fm-backlog-handoff.sh|bin/fm-backlog-receive.sh|bin/fm-procevent-remote-reply.sh|\
|
|
1133
|
+
bin/fm-config-inherit-lib.sh|bin/fm-config-push.sh|bin/fm-shared*|\
|
|
1134
|
+
bin/fm-stow-cascade.sh)
|
|
1135
|
+
printf '%s\n' secondmate
|
|
1136
|
+
;;
|
|
1137
|
+
bin/fm-session-start.sh|bin/fm-bootstrap.sh|bin/fm-fleet-sync.sh|\
|
|
1138
|
+
bin/fm-sessionstart-nudge.sh|bin/fm-startup-network.sh|bin/fm-tangle*|bin/fm-update.sh|\
|
|
1139
|
+
bin/fm-gate-refuse*|bin/fm-lock*)
|
|
1140
|
+
printf '%s\n' session-bootstrap
|
|
1141
|
+
;;
|
|
1142
|
+
bin/fm-quota-axi-lib.sh)
|
|
1143
|
+
printf '%s\n' session-bootstrap
|
|
1144
|
+
printf '%s\n' "__script__:fm-procevent-quota.test.sh"
|
|
1145
|
+
printf '%s\n' "__script__:fm-quota-choose.test.sh"
|
|
1146
|
+
;;
|
|
1147
|
+
bin/fm-procevent-quota.sh)
|
|
1148
|
+
printf '%s\n' "__script__:fm-procevent-quota.test.sh"
|
|
1149
|
+
;;
|
|
1150
|
+
bin/fm-quota-choose.sh)
|
|
1151
|
+
printf '%s\n' "__script__:fm-quota-choose.test.sh"
|
|
1152
|
+
;;
|
|
1153
|
+
bin/fm-sessionstart-run.sh|.claude/settings.json|.codex/hooks.json|\
|
|
1154
|
+
.pi/extensions/fm-primary-turnend-guard.ts)
|
|
1155
|
+
# The run tier's two harness-supplied facts (source vocabulary and
|
|
1156
|
+
# context-reset stdout injection) only show up against a real harness.
|
|
1157
|
+
printf '%s\n' session-bootstrap
|
|
1158
|
+
printf '%s\n' live-harness-optin
|
|
1159
|
+
;;
|
|
1160
|
+
bin/fm-extension.mjs|bin/fm-extension.sh|docs/examples/process-event-extension/*)
|
|
1161
|
+
printf '%s\n' __script__:fm-extension-binding.test.sh
|
|
1162
|
+
;;
|
|
1163
|
+
bin/fm-procevent.sh|bin/fm-procevent-lib.sh|bin/fm-procevent-extension-capture.pl)
|
|
1164
|
+
printf '%s\n' __script__:fm-extension-binding.test.sh
|
|
1165
|
+
printf '%s\n' __script__:fm-procevent.test.sh
|
|
1166
|
+
printf '%s\n' __script__:fm-procevent-when.test.sh
|
|
1167
|
+
printf '%s\n' __script__:fm-remote-reply.test.sh
|
|
1168
|
+
;;
|
|
1169
|
+
bin/fm-timeout-lib.sh)
|
|
1170
|
+
# The shared hard bound: session start's runtime bound, the fleet/bearings
|
|
1171
|
+
# snapshots, the vendor auth probe, the stow cascade's per-home step, and
|
|
1172
|
+
# the wedge detector's worktree write probe all depend on it.
|
|
1173
|
+
printf '%s\n' session-bootstrap
|
|
1174
|
+
printf '%s\n' snapshot-bearings
|
|
1175
|
+
printf '%s\n' pure-contract-unit
|
|
1176
|
+
printf '%s\n' secondmate
|
|
1177
|
+
printf '%s\n' watcher-wake-lock
|
|
1178
|
+
printf '%s\n' "__script__:fm-procevent-quota.test.sh"
|
|
1179
|
+
;;
|
|
1180
|
+
bin/fm-pr-*|bin/fm-merge-local.sh|bin/fm-teardown.sh|bin/fm-review-diff.sh|\
|
|
1181
|
+
bin/fm-x-*|bin/fm-check*)
|
|
1182
|
+
printf '%s\n' pr-forge
|
|
1183
|
+
;;
|
|
1184
|
+
bin/fm-nm-run-lib.sh)
|
|
1185
|
+
# Shared no-mistakes run-attribution primitives, sourced by both
|
|
1186
|
+
# bin/fm-crew-state.sh (pure-contract-unit) and bin/fm-teardown.sh's
|
|
1187
|
+
# pre-teardown run abort (pr-forge).
|
|
1188
|
+
printf '%s\n' pure-contract-unit
|
|
1189
|
+
printf '%s\n' pr-forge
|
|
1190
|
+
;;
|
|
1191
|
+
bin/fm-control-lib.sh)
|
|
1192
|
+
printf '%s\n' backend-dispatch
|
|
1193
|
+
printf '%s\n' session-bootstrap
|
|
1194
|
+
printf '%s\n' "__script__:fm-quota-choose.test.sh"
|
|
1195
|
+
;;
|
|
1196
|
+
bin/fm-composer-lib.sh)
|
|
1197
|
+
# The shared shape catalogue is vendor-rendered signal; a change to it
|
|
1198
|
+
# re-selects the live guard (fm-composer-matrix-live-e2e) alongside the
|
|
1199
|
+
# portable families.
|
|
1200
|
+
printf '%s\n' backend-dispatch
|
|
1201
|
+
printf '%s\n' pure-contract-unit
|
|
1202
|
+
printf '%s\n' live-harness-optin
|
|
1203
|
+
;;
|
|
1204
|
+
bin/fm-spawn.sh|bin/fm-send.sh|bin/fm-harness.sh|\
|
|
1205
|
+
bin/fm-peek.sh|bin/fm-composer*)
|
|
1206
|
+
printf '%s\n' backend-dispatch
|
|
1207
|
+
printf '%s\n' pure-contract-unit
|
|
1208
|
+
;;
|
|
1209
|
+
bin/fm-task-inbox-lib.sh)
|
|
1210
|
+
# The steering-inbox record/doorbell/ladder owner: fm-send's data plane
|
|
1211
|
+
# (backend-dispatch), the watcher's re-ring check (watcher-wake-lock),
|
|
1212
|
+
# and the live doorbell guard against real harnesses.
|
|
1213
|
+
printf '%s\n' backend-dispatch
|
|
1214
|
+
printf '%s\n' watcher-wake-lock
|
|
1215
|
+
printf '%s\n' live-harness-optin
|
|
1216
|
+
;;
|
|
1217
|
+
bin/fm-bearings-snapshot.sh|bin/fm-fleet-snapshot.sh|bin/fm-fleet-view.sh|\
|
|
1218
|
+
bin/fm-home-summary-refresh.sh)
|
|
1219
|
+
printf '%s\n' snapshot-bearings
|
|
1220
|
+
;;
|
|
1221
|
+
bin/fm-install-herdr.sh|bin/fm-install-treehouse.sh|bin/fm-herdr-ci-cleanup.sh)
|
|
1222
|
+
printf '%s\n' pure-contract-unit
|
|
1223
|
+
# Pin or cleanup changes also select the real-Herdr family so the required
|
|
1224
|
+
# lane's contract coverage re-runs.
|
|
1225
|
+
printf '%s\n' real-herdr-gated
|
|
1226
|
+
;;
|
|
1227
|
+
bin/fm-lint.sh|bin/fm-lint-workflows.sh|bin/fm-install-shellcheck.sh|\
|
|
1228
|
+
bin/fm-install-actionlint.sh|\
|
|
1229
|
+
bin/fm-brief.sh|bin/fm-ensure-agents-md.sh|bin/fm-crew-state.sh|\
|
|
1230
|
+
bin/fm-captain-hold.sh|bin/fm-decision-hold.sh|bin/fm-supervision*|bin/fm-transition-lib.sh|\
|
|
1231
|
+
bin/fm-tmux-lib.sh|bin/fm-marker-lib.sh|bin/fm-operational-input.sh|bin/fm-tasks-axi-lib.sh|\
|
|
1232
|
+
bin/fm-vendor-auth-probe.sh|\
|
|
1233
|
+
bin/fm-primary-scope-lib.sh|bin/fm-project-mode.sh|bin/fm-promote.sh|\
|
|
1234
|
+
bin/fm-ff-lib.sh|bin/fm-gotmp*|bin/*pretool*)
|
|
1235
|
+
printf '%s\n' pure-contract-unit
|
|
1236
|
+
;;
|
|
1237
|
+
.agents/skills/quota-array-dispatch/SKILL.md)
|
|
1238
|
+
printf '%s\n' pure-contract-unit
|
|
1239
|
+
printf '%s\n' live-harness-optin
|
|
1240
|
+
;;
|
|
1241
|
+
.agents/skills/harness-adapters/SKILL.md|.agents/skills/harness-adapters/references/*)
|
|
1242
|
+
printf '%s\n' pure-contract-unit
|
|
1243
|
+
printf '%s\n' live-harness-optin
|
|
1244
|
+
;;
|
|
1245
|
+
.agents/skills/*/SKILL.md)
|
|
1246
|
+
printf '%s\n' pure-contract-unit
|
|
1247
|
+
;;
|
|
1248
|
+
.github/workflows/ci.yml|.no-mistakes.yaml)
|
|
1249
|
+
printf '%s\n' pure-contract-unit
|
|
1250
|
+
printf '%s\n' real-herdr-gated
|
|
1251
|
+
;;
|
|
1252
|
+
docs/fm-test-portable-shards.md|docs/fm-test-isolation-proof.md|\
|
|
1253
|
+
docs/fm-test-isolation-proof.json)
|
|
1254
|
+
printf '%s\n' pure-contract-unit
|
|
1255
|
+
;;
|
|
1256
|
+
.github/*|.tasks.toml|AGENTS.md|CLAUDE.md|CONTRIBUTING.md|\
|
|
1257
|
+
docs/configuration.md|docs/supervision-protocols/*)
|
|
1258
|
+
printf '%s\n' pure-contract-unit
|
|
1259
|
+
;;
|
|
1260
|
+
tests/lib.sh|tests/*-helpers.sh|tests/fixtures.sh)
|
|
1261
|
+
families_for_test_reference "$(basename "$path")" \
|
|
1262
|
+
|| printf '%s\n' "__unmapped__:$path"
|
|
1263
|
+
;;
|
|
1264
|
+
tests/fixtures/*/*)
|
|
1265
|
+
# A fixture belongs to whichever suite reads its directory, found by the
|
|
1266
|
+
# same reference scan used for shared helpers. Keyed on the directory
|
|
1267
|
+
# rather than the file so adding a fixture selects the same suite.
|
|
1268
|
+
# A removed fixture directory has no consuming suite left to select.
|
|
1269
|
+
fixture_ref=${path#tests/fixtures/}
|
|
1270
|
+
fixture_ref=${fixture_ref%%/*}
|
|
1271
|
+
if [ -d "tests/fixtures/$fixture_ref" ]; then
|
|
1272
|
+
families_for_test_reference "fixtures/$fixture_ref" \
|
|
1273
|
+
|| printf '%s\n' "__unmapped__:$path"
|
|
1274
|
+
fi
|
|
1275
|
+
;;
|
|
1276
|
+
bin/*)
|
|
1277
|
+
# A deleted script has no consuming suite left to select, the same rule
|
|
1278
|
+
# the fixture case above applies. Refusing on its absent mapping would
|
|
1279
|
+
# make every retirement branch unable to select its changed tests.
|
|
1280
|
+
if [ -e "$path" ]; then
|
|
1281
|
+
families_for_unmapped_bin "$path" \
|
|
1282
|
+
|| printf '%s\n' "__unmapped__:$path"
|
|
1283
|
+
fi
|
|
1284
|
+
;;
|
|
1285
|
+
tests/*)
|
|
1286
|
+
printf '%s\n' "__unmapped__:$path"
|
|
1287
|
+
;;
|
|
1288
|
+
README.md|LICENSE|assets/*|docs/*|.gitignore)
|
|
1289
|
+
;;
|
|
1290
|
+
*)
|
|
1291
|
+
families_for_test_reference "$path" \
|
|
1292
|
+
|| printf '%s\n' "__unmapped__:$path"
|
|
1293
|
+
;;
|
|
1294
|
+
esac
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
select_changed() {
|
|
1298
|
+
local base=$1 path entry fam script_name s
|
|
1299
|
+
local -a wanted_families=()
|
|
1300
|
+
local -a wanted_scripts=()
|
|
1301
|
+
|
|
1302
|
+
if ! git -C "$ROOT" rev-parse --verify "$base" >/dev/null 2>&1; then
|
|
1303
|
+
die "changed-file base ref not found: $base (pass --base <ref>)"
|
|
1304
|
+
fi
|
|
1305
|
+
|
|
1306
|
+
while IFS= read -r path; do
|
|
1307
|
+
[ -n "$path" ] || continue
|
|
1308
|
+
while IFS= read -r entry; do
|
|
1309
|
+
[ -n "$entry" ] || continue
|
|
1310
|
+
case "$entry" in
|
|
1311
|
+
__script__:*)
|
|
1312
|
+
script_name=${entry#__script__:}
|
|
1313
|
+
wanted_scripts+=("$script_name")
|
|
1314
|
+
;;
|
|
1315
|
+
__unmapped__:*)
|
|
1316
|
+
die "no changed-test mapping for source path: ${entry#__unmapped__:}"
|
|
1317
|
+
;;
|
|
1318
|
+
*)
|
|
1319
|
+
wanted_families+=("$entry")
|
|
1320
|
+
;;
|
|
1321
|
+
esac
|
|
1322
|
+
done < <(families_for_changed_path "$path")
|
|
1323
|
+
done < <(git -C "$ROOT" diff --name-only "${base}...HEAD" 2>/dev/null; \
|
|
1324
|
+
git -C "$ROOT" diff --name-only HEAD 2>/dev/null; \
|
|
1325
|
+
git -C "$ROOT" ls-files --others --exclude-standard 2>/dev/null)
|
|
1326
|
+
|
|
1327
|
+
# Dedup families
|
|
1328
|
+
local f seen_f
|
|
1329
|
+
local -a unique_families=()
|
|
1330
|
+
for f in "${wanted_families[@]+"${wanted_families[@]}"}"; do
|
|
1331
|
+
seen_f=0
|
|
1332
|
+
for u in "${unique_families[@]+"${unique_families[@]}"}"; do
|
|
1333
|
+
[ "$u" = "$f" ] && { seen_f=1; break; }
|
|
1334
|
+
done
|
|
1335
|
+
[ "$seen_f" -eq 0 ] && unique_families+=("$f")
|
|
1336
|
+
done
|
|
1337
|
+
|
|
1338
|
+
for f in "${unique_families[@]+"${unique_families[@]}"}"; do
|
|
1339
|
+
while IFS= read -r s; do
|
|
1340
|
+
[ -n "$s" ] || continue
|
|
1341
|
+
if [ "$(family_for_basename "$(basename "$s")")" = "$f" ]; then
|
|
1342
|
+
add_script "$s"
|
|
1343
|
+
fi
|
|
1344
|
+
done < <(all_repo_tests)
|
|
1345
|
+
done
|
|
1346
|
+
|
|
1347
|
+
for script_name in "${wanted_scripts[@]+"${wanted_scripts[@]}"}"; do
|
|
1348
|
+
if [ -f "tests/$script_name" ]; then
|
|
1349
|
+
add_script "tests/$script_name"
|
|
1350
|
+
fi
|
|
1351
|
+
done
|
|
1352
|
+
|
|
1353
|
+
if [ "${#SCRIPTS[@]}" -eq 0 ]; then
|
|
1354
|
+
log "no tests selected for changes vs $base (map is conservative; use --all for the complete suite)"
|
|
1355
|
+
fi
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
detect_gate_skip() {
|
|
1359
|
+
# True when the first non-empty output line is a skip: gate message.
|
|
1360
|
+
local file=$1 first
|
|
1361
|
+
first=$(awk 'NF { print; exit }' "$file" 2>/dev/null || true)
|
|
1362
|
+
case "$first" in
|
|
1363
|
+
skip:*) return 0 ;;
|
|
1364
|
+
*) return 1 ;;
|
|
1365
|
+
esac
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
# True when any output line contains "skip: <token>" (token may contain spaces).
|
|
1369
|
+
detect_gate_skip_token() {
|
|
1370
|
+
local file=$1 token=$2
|
|
1371
|
+
[ -n "$token" ] || return 1
|
|
1372
|
+
grep -F -q "skip: $token" "$file" 2>/dev/null
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
apply_exclude_families() {
|
|
1376
|
+
local s fam keep ex
|
|
1377
|
+
local -a kept=()
|
|
1378
|
+
[ "${#EXCLUDE_FAMILIES[@]}" -gt 0 ] || return 0
|
|
1379
|
+
for s in "${SCRIPTS[@]+"${SCRIPTS[@]}"}"; do
|
|
1380
|
+
fam=$(family_for_basename "$(basename "$s")")
|
|
1381
|
+
keep=1
|
|
1382
|
+
for ex in "${EXCLUDE_FAMILIES[@]+"${EXCLUDE_FAMILIES[@]}"}"; do
|
|
1383
|
+
if [ "$fam" = "$ex" ]; then
|
|
1384
|
+
keep=0
|
|
1385
|
+
break
|
|
1386
|
+
fi
|
|
1387
|
+
done
|
|
1388
|
+
[ "$keep" -eq 1 ] && kept+=("$s")
|
|
1389
|
+
done
|
|
1390
|
+
SCRIPTS=("${kept[@]+"${kept[@]}"}")
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
write_json_artifact() {
|
|
1394
|
+
local out=$1
|
|
1395
|
+
local started=$2
|
|
1396
|
+
local finished=$3
|
|
1397
|
+
local run_id=$4
|
|
1398
|
+
local total=$5
|
|
1399
|
+
local failed=$6
|
|
1400
|
+
local skipped=$7
|
|
1401
|
+
local duration=$8
|
|
1402
|
+
local selection=$9
|
|
1403
|
+
local records_file=${10}
|
|
1404
|
+
local families_file=${11}
|
|
1405
|
+
|
|
1406
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
1407
|
+
die "--json requires python3 to emit a valid timing artifact"
|
|
1408
|
+
fi
|
|
1409
|
+
|
|
1410
|
+
python3 - "$out" "$started" "$finished" "$run_id" "$total" "$failed" "$skipped" "$duration" "$selection" "$records_file" "$families_file" <<'PY'
|
|
1411
|
+
import json, sys
|
|
1412
|
+
|
|
1413
|
+
out, started, finished, run_id, total, failed, skipped, duration, selection, records_file, families_file = sys.argv[1:]
|
|
1414
|
+
|
|
1415
|
+
scripts = []
|
|
1416
|
+
with open(records_file, encoding="utf-8") as fh:
|
|
1417
|
+
for line in fh:
|
|
1418
|
+
line = line.rstrip("\n")
|
|
1419
|
+
if not line:
|
|
1420
|
+
continue
|
|
1421
|
+
path, family, expected, exit_s, dur_s, gate = line.split("\t")
|
|
1422
|
+
scripts.append({
|
|
1423
|
+
"path": path,
|
|
1424
|
+
"family": family,
|
|
1425
|
+
"expected_gate_skip": expected,
|
|
1426
|
+
"duration_ms": int(dur_s),
|
|
1427
|
+
"exit": int(exit_s),
|
|
1428
|
+
"gate_skip": gate == "true",
|
|
1429
|
+
})
|
|
1430
|
+
|
|
1431
|
+
families = []
|
|
1432
|
+
with open(families_file, encoding="utf-8") as fh:
|
|
1433
|
+
for line in fh:
|
|
1434
|
+
line = line.rstrip("\n")
|
|
1435
|
+
if not line:
|
|
1436
|
+
continue
|
|
1437
|
+
name, count_s, dur_s, failed_s = line.split("\t")
|
|
1438
|
+
families.append({
|
|
1439
|
+
"name": name,
|
|
1440
|
+
"count": int(count_s),
|
|
1441
|
+
"duration_ms": int(dur_s),
|
|
1442
|
+
"failed": int(failed_s),
|
|
1443
|
+
})
|
|
1444
|
+
|
|
1445
|
+
doc = {
|
|
1446
|
+
"run_id": run_id,
|
|
1447
|
+
"started_at": started,
|
|
1448
|
+
"finished_at": finished,
|
|
1449
|
+
"selection": selection,
|
|
1450
|
+
"summary": {
|
|
1451
|
+
"total": int(total),
|
|
1452
|
+
"failed": int(failed),
|
|
1453
|
+
"skipped_gate": int(skipped),
|
|
1454
|
+
"duration_ms": int(duration),
|
|
1455
|
+
},
|
|
1456
|
+
"scripts": scripts,
|
|
1457
|
+
"families": families,
|
|
1458
|
+
}
|
|
1459
|
+
with open(out, "w", encoding="utf-8") as fh:
|
|
1460
|
+
json.dump(doc, fh, indent=2, sort_keys=True)
|
|
1461
|
+
fh.write("\n")
|
|
1462
|
+
PY
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
while [ "$#" -gt 0 ]; do
|
|
1466
|
+
case "$1" in
|
|
1467
|
+
--all)
|
|
1468
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1469
|
+
MODE=all
|
|
1470
|
+
shift
|
|
1471
|
+
;;
|
|
1472
|
+
--family)
|
|
1473
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1474
|
+
[ "$#" -gt 1 ] || die "--family requires a name"
|
|
1475
|
+
MODE=family
|
|
1476
|
+
FAMILY=$2
|
|
1477
|
+
shift 2
|
|
1478
|
+
;;
|
|
1479
|
+
--family=*)
|
|
1480
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1481
|
+
MODE=family
|
|
1482
|
+
FAMILY=${1#--family=}
|
|
1483
|
+
shift
|
|
1484
|
+
;;
|
|
1485
|
+
--lane)
|
|
1486
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1487
|
+
[ "$#" -gt 1 ] || die "--lane requires a name (see --list-lanes)"
|
|
1488
|
+
MODE=lane
|
|
1489
|
+
LANE=$2
|
|
1490
|
+
shift 2
|
|
1491
|
+
;;
|
|
1492
|
+
--lane=*)
|
|
1493
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1494
|
+
MODE=lane
|
|
1495
|
+
LANE=${1#--lane=}
|
|
1496
|
+
shift
|
|
1497
|
+
;;
|
|
1498
|
+
--proven-isolated)
|
|
1499
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1500
|
+
MODE=proven-isolated
|
|
1501
|
+
shift
|
|
1502
|
+
;;
|
|
1503
|
+
--changed)
|
|
1504
|
+
[ -z "$MODE" ] || die "only one selection mode is allowed"
|
|
1505
|
+
MODE=changed
|
|
1506
|
+
shift
|
|
1507
|
+
;;
|
|
1508
|
+
--base)
|
|
1509
|
+
[ "$#" -gt 1 ] || die "--base requires a git ref"
|
|
1510
|
+
BASE_REF=$2
|
|
1511
|
+
shift 2
|
|
1512
|
+
;;
|
|
1513
|
+
--base=*)
|
|
1514
|
+
BASE_REF=${1#--base=}
|
|
1515
|
+
shift
|
|
1516
|
+
;;
|
|
1517
|
+
--json)
|
|
1518
|
+
[ "$#" -gt 1 ] || die "--json requires a path"
|
|
1519
|
+
JSON_PATH=$2
|
|
1520
|
+
shift 2
|
|
1521
|
+
;;
|
|
1522
|
+
--json=*)
|
|
1523
|
+
JSON_PATH=${1#--json=}
|
|
1524
|
+
shift
|
|
1525
|
+
;;
|
|
1526
|
+
--jobs)
|
|
1527
|
+
[ "$#" -gt 1 ] || die "--jobs requires a positive integer"
|
|
1528
|
+
JOBS=$2
|
|
1529
|
+
JOBS_EXPLICIT=1
|
|
1530
|
+
shift 2
|
|
1531
|
+
;;
|
|
1532
|
+
--jobs=*)
|
|
1533
|
+
JOBS=${1#--jobs=}
|
|
1534
|
+
JOBS_EXPLICIT=1
|
|
1535
|
+
shift
|
|
1536
|
+
;;
|
|
1537
|
+
--max-wall-ms)
|
|
1538
|
+
[ "$#" -gt 1 ] || die "--max-wall-ms requires a positive integer"
|
|
1539
|
+
MAX_WALL_MS=$2
|
|
1540
|
+
shift 2
|
|
1541
|
+
;;
|
|
1542
|
+
--max-wall-ms=*)
|
|
1543
|
+
MAX_WALL_MS=${1#--max-wall-ms=}
|
|
1544
|
+
shift
|
|
1545
|
+
;;
|
|
1546
|
+
--per-script-timeout-secs)
|
|
1547
|
+
[ "$#" -gt 1 ] || die "--per-script-timeout-secs requires a whole number of seconds"
|
|
1548
|
+
PER_SCRIPT_TIMEOUT_SECS=$2
|
|
1549
|
+
shift 2
|
|
1550
|
+
;;
|
|
1551
|
+
--per-script-timeout-secs=*)
|
|
1552
|
+
PER_SCRIPT_TIMEOUT_SECS=${1#--per-script-timeout-secs=}
|
|
1553
|
+
shift
|
|
1554
|
+
;;
|
|
1555
|
+
--list)
|
|
1556
|
+
LIST_ONLY=1
|
|
1557
|
+
shift
|
|
1558
|
+
;;
|
|
1559
|
+
--list-scheduled)
|
|
1560
|
+
LIST_SCHEDULED=1
|
|
1561
|
+
shift
|
|
1562
|
+
;;
|
|
1563
|
+
--list-families)
|
|
1564
|
+
LIST_FAMILIES=1
|
|
1565
|
+
shift
|
|
1566
|
+
;;
|
|
1567
|
+
--list-concurrent-safe-families)
|
|
1568
|
+
LIST_CONCURRENT_SAFE_FAMILIES=1
|
|
1569
|
+
shift
|
|
1570
|
+
;;
|
|
1571
|
+
--concurrent-safe-family-jobs-max)
|
|
1572
|
+
[ "$#" -gt 1 ] || die "--concurrent-safe-family-jobs-max requires a family name"
|
|
1573
|
+
concurrent_safe_family_jobs_max "$2"
|
|
1574
|
+
exit 0
|
|
1575
|
+
;;
|
|
1576
|
+
--concurrent-safe-family-jobs-max=*)
|
|
1577
|
+
concurrent_safe_family_jobs_max "${1#--concurrent-safe-family-jobs-max=}"
|
|
1578
|
+
exit 0
|
|
1579
|
+
;;
|
|
1580
|
+
--list-lanes)
|
|
1581
|
+
LIST_LANES=1
|
|
1582
|
+
shift
|
|
1583
|
+
;;
|
|
1584
|
+
--check-coverage)
|
|
1585
|
+
CHECK_COVERAGE=1
|
|
1586
|
+
shift
|
|
1587
|
+
;;
|
|
1588
|
+
--aggregate-json)
|
|
1589
|
+
[ "$#" -gt 1 ] || die "--aggregate-json requires an output path"
|
|
1590
|
+
AGGREGATE_OUT=$2
|
|
1591
|
+
shift 2
|
|
1592
|
+
# Remaining args after options will be collected as inputs below via MODE.
|
|
1593
|
+
# For aggregation we accept only input JSON paths as free args after this.
|
|
1594
|
+
MODE=aggregate
|
|
1595
|
+
;;
|
|
1596
|
+
--exclude-family)
|
|
1597
|
+
[ "$#" -gt 1 ] || die "--exclude-family requires a name"
|
|
1598
|
+
EXCLUDE_FAMILIES+=("$2")
|
|
1599
|
+
shift 2
|
|
1600
|
+
;;
|
|
1601
|
+
--exclude-family=*)
|
|
1602
|
+
EXCLUDE_FAMILIES+=("${1#--exclude-family=}")
|
|
1603
|
+
shift
|
|
1604
|
+
;;
|
|
1605
|
+
--fail-on-gate-skip)
|
|
1606
|
+
[ "$#" -gt 1 ] || die "--fail-on-gate-skip requires a token (e.g. 'herdr not found')"
|
|
1607
|
+
FAIL_ON_GATE_SKIP=$2
|
|
1608
|
+
shift 2
|
|
1609
|
+
;;
|
|
1610
|
+
--fail-on-gate-skip=*)
|
|
1611
|
+
FAIL_ON_GATE_SKIP=${1#--fail-on-gate-skip=}
|
|
1612
|
+
shift
|
|
1613
|
+
;;
|
|
1614
|
+
-h|--help)
|
|
1615
|
+
usage
|
|
1616
|
+
exit 0
|
|
1617
|
+
;;
|
|
1618
|
+
--)
|
|
1619
|
+
shift
|
|
1620
|
+
while [ "$#" -gt 0 ]; do
|
|
1621
|
+
SCRIPTS+=("$1")
|
|
1622
|
+
shift
|
|
1623
|
+
done
|
|
1624
|
+
;;
|
|
1625
|
+
-*)
|
|
1626
|
+
die "unknown option: $1"
|
|
1627
|
+
;;
|
|
1628
|
+
*)
|
|
1629
|
+
if [ "${MODE:-}" = "aggregate" ]; then
|
|
1630
|
+
SCRIPTS+=("$1")
|
|
1631
|
+
elif [ -z "$MODE" ] || [ "$MODE" = scripts ]; then
|
|
1632
|
+
MODE=scripts
|
|
1633
|
+
SCRIPTS+=("$1")
|
|
1634
|
+
else
|
|
1635
|
+
die "script paths cannot be combined with --$MODE"
|
|
1636
|
+
fi
|
|
1637
|
+
shift
|
|
1638
|
+
;;
|
|
1639
|
+
esac
|
|
1640
|
+
done
|
|
1641
|
+
|
|
1642
|
+
if [ "$LIST_FAMILIES" -eq 1 ]; then
|
|
1643
|
+
list_known_families
|
|
1644
|
+
exit 0
|
|
1645
|
+
fi
|
|
1646
|
+
|
|
1647
|
+
if [ "$LIST_CONCURRENT_SAFE_FAMILIES" -eq 1 ]; then
|
|
1648
|
+
list_concurrent_safe_families
|
|
1649
|
+
exit 0
|
|
1650
|
+
fi
|
|
1651
|
+
|
|
1652
|
+
if [ "$LIST_LANES" -eq 1 ]; then
|
|
1653
|
+
list_known_lanes
|
|
1654
|
+
exit 0
|
|
1655
|
+
fi
|
|
1656
|
+
|
|
1657
|
+
if [ "$CHECK_COVERAGE" -eq 1 ]; then
|
|
1658
|
+
run_coverage_guard
|
|
1659
|
+
exit $?
|
|
1660
|
+
fi
|
|
1661
|
+
|
|
1662
|
+
if [ "${MODE:-}" = "aggregate" ]; then
|
|
1663
|
+
[ -n "$AGGREGATE_OUT" ] || die "--aggregate-json requires an output path"
|
|
1664
|
+
[ "${#SCRIPTS[@]}" -gt 0 ] || die "--aggregate-json requires at least one input timing JSON"
|
|
1665
|
+
for s in "${SCRIPTS[@]}"; do
|
|
1666
|
+
[ -f "$s" ] || die "aggregate input not found: $s"
|
|
1667
|
+
done
|
|
1668
|
+
aggregate_timing_json "$AGGREGATE_OUT" "${SCRIPTS[@]}"
|
|
1669
|
+
exit 0
|
|
1670
|
+
fi
|
|
1671
|
+
|
|
1672
|
+
case "$JOBS" in
|
|
1673
|
+
''|*[!0-9]*) die "--jobs must be a positive integer" ;;
|
|
1674
|
+
esac
|
|
1675
|
+
[ "$JOBS" -ge 1 ] || die "--jobs must be >= 1"
|
|
1676
|
+
[ "$JOBS" -le "$JOBS_MAX" ] || die "--jobs is capped at $JOBS_MAX (got $JOBS)"
|
|
1677
|
+
|
|
1678
|
+
if [ -n "$MAX_WALL_MS" ]; then
|
|
1679
|
+
case "$MAX_WALL_MS" in
|
|
1680
|
+
''|*[!0-9]*) die "--max-wall-ms requires a positive integer" ;;
|
|
1681
|
+
esac
|
|
1682
|
+
[ "$MAX_WALL_MS" -gt 0 ] || die "--max-wall-ms requires a positive integer"
|
|
1683
|
+
fi
|
|
1684
|
+
|
|
1685
|
+
case "$PER_SCRIPT_TIMEOUT_SECS" in
|
|
1686
|
+
''|*[!0-9]*) die "--per-script-timeout-secs requires a whole number of seconds (0 disables)" ;;
|
|
1687
|
+
esac
|
|
1688
|
+
|
|
1689
|
+
case "${MODE:-}" in
|
|
1690
|
+
all)
|
|
1691
|
+
select_all
|
|
1692
|
+
SELECTION_DESC="all"
|
|
1693
|
+
;;
|
|
1694
|
+
family)
|
|
1695
|
+
select_family "$FAMILY"
|
|
1696
|
+
SELECTION_DESC="family=$FAMILY"
|
|
1697
|
+
;;
|
|
1698
|
+
lane)
|
|
1699
|
+
select_lane "$LANE"
|
|
1700
|
+
SELECTION_DESC="lane=$LANE"
|
|
1701
|
+
;;
|
|
1702
|
+
proven-isolated)
|
|
1703
|
+
select_proven_isolated
|
|
1704
|
+
SELECTION_DESC="proven-isolated"
|
|
1705
|
+
;;
|
|
1706
|
+
changed)
|
|
1707
|
+
select_changed "$BASE_REF"
|
|
1708
|
+
SELECTION_DESC="changed:base=$BASE_REF"
|
|
1709
|
+
;;
|
|
1710
|
+
scripts)
|
|
1711
|
+
# Normalize and re-add through add_script for consistent paths.
|
|
1712
|
+
raw=("${SCRIPTS[@]+"${SCRIPTS[@]}"}")
|
|
1713
|
+
SCRIPTS=()
|
|
1714
|
+
for s in "${raw[@]}"; do
|
|
1715
|
+
add_script "$s"
|
|
1716
|
+
done
|
|
1717
|
+
SELECTION_DESC="scripts"
|
|
1718
|
+
;;
|
|
1719
|
+
*)
|
|
1720
|
+
die "select with --all, --family <name>, --lane <name>, --proven-isolated, --changed, or one or more script paths (see --help)"
|
|
1721
|
+
;;
|
|
1722
|
+
esac
|
|
1723
|
+
|
|
1724
|
+
apply_exclude_families
|
|
1725
|
+
if [ "${#EXCLUDE_FAMILIES[@]}" -gt 0 ]; then
|
|
1726
|
+
SELECTION_DESC="${SELECTION_DESC};exclude-family=$(IFS=,; printf '%s' "${EXCLUDE_FAMILIES[*]}")"
|
|
1727
|
+
fi
|
|
1728
|
+
if [ -n "$FAIL_ON_GATE_SKIP" ]; then
|
|
1729
|
+
SELECTION_DESC="${SELECTION_DESC};fail-on-gate-skip=$FAIL_ON_GATE_SKIP"
|
|
1730
|
+
fi
|
|
1731
|
+
if [ "$LIST_ONLY" -eq 1 ] || [ "$LIST_SCHEDULED" -eq 1 ]; then
|
|
1732
|
+
if [ "$LIST_SCHEDULED" -eq 1 ]; then
|
|
1733
|
+
for s in "${SCRIPTS[@]+"${SCRIPTS[@]}"}"; do
|
|
1734
|
+
printf '%s\t%s\n' "$(portable_serial_weight_for "$s")" "$s"
|
|
1735
|
+
done | LC_ALL=C sort -t"$(printf '\t')" -k1,1nr -k2,2 | cut -f2-
|
|
1736
|
+
else
|
|
1737
|
+
for s in "${SCRIPTS[@]+"${SCRIPTS[@]}"}"; do
|
|
1738
|
+
printf '%s\n' "$s"
|
|
1739
|
+
done
|
|
1740
|
+
fi
|
|
1741
|
+
exit 0
|
|
1742
|
+
fi
|
|
1743
|
+
|
|
1744
|
+
# An empty selection is a clean result, not a no-op that falls through. Exiting
|
|
1745
|
+
# here also keeps every array expansion below off the empty-array path: under
|
|
1746
|
+
# `set -u`, bash 3.2 (the stock macOS shell) treats "${arr[@]}" on an empty
|
|
1747
|
+
# array as an unbound-variable error, while bash 4.4+ makes it a harmless no-op.
|
|
1748
|
+
# A contributor on stock macOS who changes only documentation must still get
|
|
1749
|
+
# total=0 and exit 0 rather than a crash.
|
|
1750
|
+
if [ "${#SCRIPTS[@]}" -eq 0 ]; then
|
|
1751
|
+
log "nothing to run"
|
|
1752
|
+
empty_finished_ms=$(now_ms)
|
|
1753
|
+
empty_duration=$((empty_finished_ms - RUN_STARTED_MS))
|
|
1754
|
+
[ "$empty_duration" -ge 0 ] || empty_duration=0
|
|
1755
|
+
empty_rc=0
|
|
1756
|
+
printf 'FM_TEST_SUMMARY total=0 failed=0 skipped_gate=0 duration_ms=%s\n' "$empty_duration"
|
|
1757
|
+
# The budget covers the whole invocation, so a selection phase that outran it
|
|
1758
|
+
# still fails - reporting zero work is not the same as reporting no time.
|
|
1759
|
+
if [ -n "$MAX_WALL_MS" ]; then
|
|
1760
|
+
printf 'FM_TEST_BUDGET max_wall_ms=%s duration_ms=%s\n' "$MAX_WALL_MS" "$empty_duration"
|
|
1761
|
+
if [ "$empty_duration" -gt "$MAX_WALL_MS" ]; then
|
|
1762
|
+
log "wall-clock budget exceeded: ${empty_duration}ms > ${MAX_WALL_MS}ms for $SELECTION_DESC"
|
|
1763
|
+
empty_rc=1
|
|
1764
|
+
fi
|
|
1765
|
+
fi
|
|
1766
|
+
if [ -n "$JSON_PATH" ]; then
|
|
1767
|
+
empty_rec=$(mktemp)
|
|
1768
|
+
empty_fam=$(mktemp)
|
|
1769
|
+
: >"$empty_rec"
|
|
1770
|
+
: >"$empty_fam"
|
|
1771
|
+
empty_finished_iso=$(now_iso)
|
|
1772
|
+
mkdir -p "$(dirname "$JSON_PATH")"
|
|
1773
|
+
write_json_artifact "$JSON_PATH" "$RUN_STARTED_ISO" "$empty_finished_iso" \
|
|
1774
|
+
"fm-test-run-${RUN_STARTED_MS}-$$" 0 0 0 "$empty_duration" \
|
|
1775
|
+
"$SELECTION_DESC" "$empty_rec" "$empty_fam"
|
|
1776
|
+
rm -f "$empty_rec" "$empty_fam"
|
|
1777
|
+
fi
|
|
1778
|
+
exit "$empty_rc"
|
|
1779
|
+
fi
|
|
1780
|
+
|
|
1781
|
+
# Verify selected scripts exist before starting.
|
|
1782
|
+
for s in "${SCRIPTS[@]}"; do
|
|
1783
|
+
[ -f "$s" ] || die "test script not found: $s"
|
|
1784
|
+
[ -x "$s" ] || [ -r "$s" ] || die "test script not readable: $s"
|
|
1785
|
+
done
|
|
1786
|
+
|
|
1787
|
+
# Plain --changed uses the bounded representative-suite scheduler; numeric
|
|
1788
|
+
# --jobs retains the strict all-script admission rule below.
|
|
1789
|
+
AUTO_CONCURRENCY=0
|
|
1790
|
+
if [ "$MODE" = changed ] && [ "$JOBS_EXPLICIT" -eq 0 ]; then
|
|
1791
|
+
if [ "${#SCRIPTS[@]}" -gt 0 ] && [ "$PER_SCRIPT_TIMEOUT_SECS" -eq 0 ]; then
|
|
1792
|
+
PER_SCRIPT_TIMEOUT_SECS=$CHANGED_DEFAULT_TIMEOUT_SECS
|
|
1793
|
+
fi
|
|
1794
|
+
auto_admissible=0
|
|
1795
|
+
for s in "${SCRIPTS[@]}"; do
|
|
1796
|
+
script_allows_concurrency "$s" && auto_admissible=$((auto_admissible + 1))
|
|
1797
|
+
done
|
|
1798
|
+
if [ "$auto_admissible" -gt 1 ]; then
|
|
1799
|
+
JOBS=$(cpu_count)
|
|
1800
|
+
[ "$JOBS" -le 4 ] || JOBS=4
|
|
1801
|
+
[ "$JOBS" -ge 1 ] || JOBS=1
|
|
1802
|
+
[ "$JOBS" -eq 1 ] || AUTO_CONCURRENCY=1
|
|
1803
|
+
fi
|
|
1804
|
+
fi
|
|
1805
|
+
if [ "$JOBS" -gt 1 ] || [ "$MODE" = changed ]; then
|
|
1806
|
+
SELECTION_DESC="${SELECTION_DESC};jobs=$JOBS"
|
|
1807
|
+
fi
|
|
1808
|
+
|
|
1809
|
+
# An explicit --jobs names a concurrency for exactly the selection given, so an
|
|
1810
|
+
# unproven script in it is a refusal rather than something to schedule around.
|
|
1811
|
+
if [ "$JOBS" -gt 1 ] && [ "$AUTO_CONCURRENCY" -eq 0 ]; then
|
|
1812
|
+
for s in "${SCRIPTS[@]}"; do
|
|
1813
|
+
if ! script_allows_concurrency "$s"; then
|
|
1814
|
+
die "--jobs $JOBS refused: $s is not in the proven-isolated set (see bin/fm-test-isolation-proof.sh --list) and its family has no recorded concurrent proof. Unproven stateful scripts stay serial."
|
|
1815
|
+
fi
|
|
1816
|
+
if ! is_proven_isolated_script "$s"; then
|
|
1817
|
+
family=$(family_for_basename "$(basename "$s")")
|
|
1818
|
+
family_jobs_max=$(concurrent_safe_family_jobs_max "$family")
|
|
1819
|
+
[ "$JOBS" -le "$family_jobs_max" ] \
|
|
1820
|
+
|| die "--jobs $JOBS refused: family $family is proven only up to $family_jobs_max concurrent workers"
|
|
1821
|
+
fi
|
|
1822
|
+
done
|
|
1823
|
+
fi
|
|
1824
|
+
|
|
1825
|
+
# Split the run into the proven-concurrent scripts and an unproven remainder.
|
|
1826
|
+
# The remainder runs serially AFTER the concurrent group, never beside it, so an
|
|
1827
|
+
# unproven script still never shares a machine with another test. An explicit
|
|
1828
|
+
# --jobs refused above, so its remainder is always empty.
|
|
1829
|
+
CONCURRENT_SCRIPTS=()
|
|
1830
|
+
SERIAL_TAIL_SCRIPTS=()
|
|
1831
|
+
if [ "$JOBS" -gt 1 ]; then
|
|
1832
|
+
SCHEDULE_TMP=$(mktemp "${TMPDIR:-/tmp}/fm-test-sched.XXXXXX")
|
|
1833
|
+
: >"$SCHEDULE_TMP"
|
|
1834
|
+
# Two passes: the tail array must be built in this shell, so the weighted
|
|
1835
|
+
# listing is written to a file rather than piped into sort from a loop whose
|
|
1836
|
+
# appends would be lost in a subshell.
|
|
1837
|
+
for s in "${SCRIPTS[@]}"; do
|
|
1838
|
+
if script_allows_concurrency "$s"; then
|
|
1839
|
+
# Longest first: workers are handed scripts in order, so starting the
|
|
1840
|
+
# longest last strands it running alone at the tail. Measured over the
|
|
1841
|
+
# watcher family, alphabetical order finished in 395s where the balanced
|
|
1842
|
+
# four-worker sum was 205s.
|
|
1843
|
+
printf '%s\t%s\n' "$(portable_serial_weight_for "$s")" "$s" >>"$SCHEDULE_TMP"
|
|
1844
|
+
else
|
|
1845
|
+
SERIAL_TAIL_SCRIPTS+=("$s")
|
|
1846
|
+
fi
|
|
1847
|
+
done
|
|
1848
|
+
while IFS=$'\t' read -r _weight s; do
|
|
1849
|
+
[ -n "$s" ] || continue
|
|
1850
|
+
CONCURRENT_SCRIPTS+=("$s")
|
|
1851
|
+
done < <(LC_ALL=C sort -t"$(printf '\t')" -k1,1nr -k2,2 "$SCHEDULE_TMP")
|
|
1852
|
+
rm -f "$SCHEDULE_TMP"
|
|
1853
|
+
fi
|
|
1854
|
+
|
|
1855
|
+
if [ "$PER_SCRIPT_TIMEOUT_SECS" -gt 0 ]; then
|
|
1856
|
+
[ -r "$ROOT/bin/fm-timeout-lib.sh" ] || die "per-script timeout helper not found: bin/fm-timeout-lib.sh"
|
|
1857
|
+
# shellcheck source=bin/fm-timeout-lib.sh
|
|
1858
|
+
. "$ROOT/bin/fm-timeout-lib.sh"
|
|
1859
|
+
fi
|
|
1860
|
+
|
|
1861
|
+
RUN_TMP=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run.XXXXXX")
|
|
1862
|
+
RECORDS="$RUN_TMP/records.tsv"
|
|
1863
|
+
FAMILIES_TSV="$RUN_TMP/families.tsv"
|
|
1864
|
+
: >"$RECORDS"
|
|
1865
|
+
declare -a WORKER_PIDS=()
|
|
1866
|
+
declare -a WORKER_IDX=()
|
|
1867
|
+
declare -a WORKER_SCRIPTS=()
|
|
1868
|
+
|
|
1869
|
+
# Invoked indirectly by the EXIT trap below.
|
|
1870
|
+
# shellcheck disable=SC2329
|
|
1871
|
+
cleanup_run() {
|
|
1872
|
+
rm -rf "$RUN_TMP"
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
trap cleanup_run EXIT
|
|
1876
|
+
|
|
1877
|
+
RUN_ID="fm-test-run-${RUN_STARTED_MS}-$$"
|
|
1878
|
+
TOTAL=0
|
|
1879
|
+
FAILED=0
|
|
1880
|
+
SKIPPED_GATE=0
|
|
1881
|
+
AGG_RC=0
|
|
1882
|
+
|
|
1883
|
+
# Family accumulators as TSV lines updated in-memory via temp files.
|
|
1884
|
+
# family -> count, duration_ms, failed
|
|
1885
|
+
family_bump() {
|
|
1886
|
+
local fam=$1 dur=$2 failed_delta=$3
|
|
1887
|
+
local line name count duration failed_count rest
|
|
1888
|
+
local found=0
|
|
1889
|
+
local tmp="$RUN_TMP/families.new"
|
|
1890
|
+
: >"$tmp"
|
|
1891
|
+
if [ -s "$FAMILIES_TSV" ]; then
|
|
1892
|
+
while IFS= read -r line; do
|
|
1893
|
+
name=${line%%$'\t'*}
|
|
1894
|
+
rest=${line#*$'\t'}
|
|
1895
|
+
count=${rest%%$'\t'*}
|
|
1896
|
+
rest=${rest#*$'\t'}
|
|
1897
|
+
duration=${rest%%$'\t'*}
|
|
1898
|
+
failed_count=${rest#*$'\t'}
|
|
1899
|
+
if [ "$name" = "$fam" ]; then
|
|
1900
|
+
count=$((count + 1))
|
|
1901
|
+
duration=$((duration + dur))
|
|
1902
|
+
failed_count=$((failed_count + failed_delta))
|
|
1903
|
+
found=1
|
|
1904
|
+
fi
|
|
1905
|
+
printf '%s\t%s\t%s\t%s\n' "$name" "$count" "$duration" "$failed_count" >>"$tmp"
|
|
1906
|
+
done <"$FAMILIES_TSV"
|
|
1907
|
+
fi
|
|
1908
|
+
if [ "$found" -eq 0 ]; then
|
|
1909
|
+
printf '%s\t%s\t%s\t%s\n' "$fam" 1 "$dur" "$failed_delta" >>"$tmp"
|
|
1910
|
+
fi
|
|
1911
|
+
mv "$tmp" "$FAMILIES_TSV"
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
record_script_result() {
|
|
1915
|
+
local script=$1 rc=$2 duration=$3 out=$4 end_iso=$5
|
|
1916
|
+
local base family expected gate_skip fail_delta
|
|
1917
|
+
base=$(basename "$script")
|
|
1918
|
+
family=$(family_for_basename "$base")
|
|
1919
|
+
expected=$(expected_gate_skip_for_family "$family")
|
|
1920
|
+
|
|
1921
|
+
if [ -n "$FAIL_ON_GATE_SKIP" ] && detect_gate_skip_token "$out" "$FAIL_ON_GATE_SKIP"; then
|
|
1922
|
+
log "required gate skip token seen in $script: skip: $FAIL_ON_GATE_SKIP"
|
|
1923
|
+
rc=1
|
|
1924
|
+
fi
|
|
1925
|
+
|
|
1926
|
+
gate_skip=false
|
|
1927
|
+
if [ "$rc" -eq 0 ] && detect_gate_skip "$out"; then
|
|
1928
|
+
gate_skip=true
|
|
1929
|
+
SKIPPED_GATE=$((SKIPPED_GATE + 1))
|
|
1930
|
+
fi
|
|
1931
|
+
|
|
1932
|
+
printf 'FM_TEST_END %s %s exit=%s duration_ms=%s gate_skip=%s\n' \
|
|
1933
|
+
"$end_iso" "$script" "$rc" "$duration" "$gate_skip"
|
|
1934
|
+
|
|
1935
|
+
fail_delta=0
|
|
1936
|
+
if [ "$rc" -ne 0 ]; then
|
|
1937
|
+
FAILED=$((FAILED + 1))
|
|
1938
|
+
fail_delta=1
|
|
1939
|
+
AGG_RC=1
|
|
1940
|
+
fi
|
|
1941
|
+
|
|
1942
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
1943
|
+
"$script" "$family" "$expected" "$rc" "$duration" "$gate_skip" >>"$RECORDS"
|
|
1944
|
+
family_bump "$family" "$duration" "$fail_delta"
|
|
1945
|
+
TOTAL=$((TOTAL + 1))
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
# Run <script>, capturing output to <out>. <stream> 1 also echoes it live.
|
|
1949
|
+
# <id> only has to be unique within this run. When PER_SCRIPT_TIMEOUT_SECS is
|
|
1950
|
+
# positive, a script that outruns it is terminated and reported as exit 124: a
|
|
1951
|
+
# hung script must become a bounded failure rather than an unbounded suite,
|
|
1952
|
+
# because an unbounded suite is what silently outruns its caller's budget.
|
|
1953
|
+
run_script_bounded() { # <script> <out> <stream> <id>
|
|
1954
|
+
local script=$1 out=$2 stream=$3 id=$4
|
|
1955
|
+
local rc
|
|
1956
|
+
: "$id"
|
|
1957
|
+
set +e
|
|
1958
|
+
if [ "$stream" -eq 1 ]; then
|
|
1959
|
+
if [ "$PER_SCRIPT_TIMEOUT_SECS" -gt 0 ]; then
|
|
1960
|
+
# Expansion is intentionally deferred to the child bash passed to -c.
|
|
1961
|
+
# shellcheck disable=SC2016
|
|
1962
|
+
fm_run_timed "$PER_SCRIPT_TIMEOUT_SECS" bash -c \
|
|
1963
|
+
'bash "$1" 2>&1 | tee "$2"; exit "${PIPESTATUS[0]}"' _ "$script" "$out"
|
|
1964
|
+
rc=$?
|
|
1965
|
+
else
|
|
1966
|
+
bash "$script" 2>&1 | tee "$out"
|
|
1967
|
+
rc=${PIPESTATUS[0]}
|
|
1968
|
+
fi
|
|
1969
|
+
elif [ "$PER_SCRIPT_TIMEOUT_SECS" -gt 0 ]; then
|
|
1970
|
+
fm_run_timed "$PER_SCRIPT_TIMEOUT_SECS" bash "$script" >"$out" 2>&1
|
|
1971
|
+
rc=$?
|
|
1972
|
+
else
|
|
1973
|
+
bash "$script" >"$out" 2>&1
|
|
1974
|
+
rc=$?
|
|
1975
|
+
fi
|
|
1976
|
+
if [ "$PER_SCRIPT_TIMEOUT_SECS" -gt 0 ] && [ "$rc" -eq 124 ]; then
|
|
1977
|
+
printf 'not ok - %s exceeded the per-script bound of %ss and was terminated\n' \
|
|
1978
|
+
"$script" "$PER_SCRIPT_TIMEOUT_SECS" >>"$out"
|
|
1979
|
+
[ "$stream" -eq 1 ] && tail -1 "$out"
|
|
1980
|
+
fi
|
|
1981
|
+
return "$rc"
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
run_one_serial() {
|
|
1985
|
+
local script=$1
|
|
1986
|
+
local base family expected out begin_iso begin_ms end_ms end_iso duration rc
|
|
1987
|
+
base=$(basename "$script")
|
|
1988
|
+
family=$(family_for_basename "$base")
|
|
1989
|
+
expected=$(expected_gate_skip_for_family "$family")
|
|
1990
|
+
out="$RUN_TMP/out.$TOTAL"
|
|
1991
|
+
begin_iso=$(now_iso)
|
|
1992
|
+
begin_ms=$(now_ms)
|
|
1993
|
+
|
|
1994
|
+
printf 'FM_TEST_BEGIN %s %s family=%s expected_gate_skip=%s\n' \
|
|
1995
|
+
"$begin_iso" "$script" "$family" "$expected"
|
|
1996
|
+
|
|
1997
|
+
set +e
|
|
1998
|
+
# Stream live output while retaining a copy for gate-skip detection.
|
|
1999
|
+
run_script_bounded "$script" "$out" 1 "s$TOTAL"
|
|
2000
|
+
rc=$?
|
|
2001
|
+
set -e
|
|
2002
|
+
: "${rc:=1}"
|
|
2003
|
+
|
|
2004
|
+
end_ms=$(now_ms)
|
|
2005
|
+
end_iso=$(now_iso)
|
|
2006
|
+
duration=$((end_ms - begin_ms))
|
|
2007
|
+
if [ "$duration" -lt 0 ]; then
|
|
2008
|
+
duration=0
|
|
2009
|
+
fi
|
|
2010
|
+
record_script_result "$script" "$rc" "$duration" "$out" "$end_iso"
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
if [ "$JOBS" -eq 1 ]; then
|
|
2014
|
+
for script in "${SCRIPTS[@]}"; do
|
|
2015
|
+
run_one_serial "$script"
|
|
2016
|
+
done
|
|
2017
|
+
else
|
|
2018
|
+
# Bounded concurrent execution for admitted scripts. Each worker gets a
|
|
2019
|
+
# private mode-0700 TMPDIR so mktemp roots cannot collide. Retries are never
|
|
2020
|
+
# used as a green strategy.
|
|
2021
|
+
worker_n=0
|
|
2022
|
+
active_workers=0
|
|
2023
|
+
|
|
2024
|
+
wait_one_job_worker() {
|
|
2025
|
+
local slot=$1 pid idx work script rc duration mode out end_iso
|
|
2026
|
+
pid=${WORKER_PIDS[$slot]}
|
|
2027
|
+
idx=${WORKER_IDX[$slot]}
|
|
2028
|
+
script=${WORKER_SCRIPTS[$slot]}
|
|
2029
|
+
set +e
|
|
2030
|
+
wait "$pid"
|
|
2031
|
+
set -e
|
|
2032
|
+
unset 'WORKER_PIDS[slot]'
|
|
2033
|
+
unset 'WORKER_IDX[slot]'
|
|
2034
|
+
unset 'WORKER_SCRIPTS[slot]'
|
|
2035
|
+
active_workers=$((active_workers - 1))
|
|
2036
|
+
work="$RUN_TMP/w$idx"
|
|
2037
|
+
rc=$(cat "$work/exit" 2>/dev/null || echo 1)
|
|
2038
|
+
duration=$(cat "$work/duration_ms" 2>/dev/null || echo 0)
|
|
2039
|
+
out="$work/output"
|
|
2040
|
+
end_iso=$(now_iso)
|
|
2041
|
+
# Replay captured output after the worker finishes so markers stay ordered.
|
|
2042
|
+
if [ -s "$out" ]; then
|
|
2043
|
+
cat "$out"
|
|
2044
|
+
fi
|
|
2045
|
+
mode=$(stat -c %a "$work" 2>/dev/null || stat -f %Lp "$work" 2>/dev/null || echo unknown)
|
|
2046
|
+
case "$mode" in
|
|
2047
|
+
700|0700) ;;
|
|
2048
|
+
*)
|
|
2049
|
+
log "isolation failure: worker root mode is $mode, expected 0700 ($work)"
|
|
2050
|
+
rc=1
|
|
2051
|
+
;;
|
|
2052
|
+
esac
|
|
2053
|
+
record_script_result "$script" "$rc" "$duration" "$out" "$end_iso"
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
worker_pid_is_running() {
|
|
2057
|
+
local want=$1 running inventory="$RUN_TMP/running-pids"
|
|
2058
|
+
# Keep `jobs` in this shell. A process substitution runs it in a subshell
|
|
2059
|
+
# without this shell's job table on Bash 3.2/5.x, falsely reporting every
|
|
2060
|
+
# worker complete and making the scheduler wait for the oldest PID.
|
|
2061
|
+
jobs -r -p >"$inventory"
|
|
2062
|
+
while IFS= read -r running; do
|
|
2063
|
+
[ "$running" = "$want" ] && return 0
|
|
2064
|
+
done <"$inventory"
|
|
2065
|
+
return 1
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
wait_one_completed_job_worker() {
|
|
2069
|
+
local slot work
|
|
2070
|
+
while :; do
|
|
2071
|
+
for slot in "${!WORKER_PIDS[@]}"; do
|
|
2072
|
+
work="$RUN_TMP/w${WORKER_IDX[$slot]}"
|
|
2073
|
+
if [ -f "$work/exit" ] || ! worker_pid_is_running "${WORKER_PIDS[$slot]}"; then
|
|
2074
|
+
wait_one_job_worker "$slot"
|
|
2075
|
+
return
|
|
2076
|
+
fi
|
|
2077
|
+
done
|
|
2078
|
+
sleep 0.01
|
|
2079
|
+
done
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
for script in "${CONCURRENT_SCRIPTS[@]+"${CONCURRENT_SCRIPTS[@]}"}"; do
|
|
2083
|
+
while [ "$active_workers" -ge "$JOBS" ]; do
|
|
2084
|
+
wait_one_completed_job_worker
|
|
2085
|
+
done
|
|
2086
|
+
worker_n=$((worker_n + 1))
|
|
2087
|
+
work="$RUN_TMP/w$worker_n"
|
|
2088
|
+
mkdir -p "$work/tmp"
|
|
2089
|
+
chmod 0700 "$work" "$work/tmp" || die "could not chmod 0700 worker root $work"
|
|
2090
|
+
base=$(basename "$script")
|
|
2091
|
+
family=$(family_for_basename "$base")
|
|
2092
|
+
expected=$(expected_gate_skip_for_family "$family")
|
|
2093
|
+
printf 'FM_TEST_BEGIN %s %s family=%s expected_gate_skip=%s\n' \
|
|
2094
|
+
"$(now_iso)" "$script" "$family" "$expected"
|
|
2095
|
+
(
|
|
2096
|
+
trap - EXIT HUP INT TERM
|
|
2097
|
+
set +e
|
|
2098
|
+
export TMPDIR="$work/tmp"
|
|
2099
|
+
export TMP="$work/tmp"
|
|
2100
|
+
unset FM_HOME FM_STATE_OVERRIDE FM_DATA_OVERRIDE FM_ROOT_OVERRIDE \
|
|
2101
|
+
FM_PROJECTS_OVERRIDE FM_CONFIG_OVERRIDE FM_BACKEND 2>/dev/null || true
|
|
2102
|
+
cd "$ROOT" || exit 1
|
|
2103
|
+
begin_ms=$(now_ms)
|
|
2104
|
+
set +e
|
|
2105
|
+
run_script_bounded "$script" "$work/output" 0 "w$worker_n"
|
|
2106
|
+
rc=$?
|
|
2107
|
+
set -e
|
|
2108
|
+
end_ms=$(now_ms)
|
|
2109
|
+
duration=$((end_ms - begin_ms))
|
|
2110
|
+
if [ "$duration" -lt 0 ]; then
|
|
2111
|
+
duration=0
|
|
2112
|
+
fi
|
|
2113
|
+
printf '%s\n' "$duration" >"$work/duration_ms"
|
|
2114
|
+
printf '%s\n' "$rc" >"$work/exit"
|
|
2115
|
+
exit 0
|
|
2116
|
+
) &
|
|
2117
|
+
worker_pid=$!
|
|
2118
|
+
WORKER_PIDS[worker_n]=$worker_pid
|
|
2119
|
+
WORKER_IDX[worker_n]=$worker_n
|
|
2120
|
+
WORKER_SCRIPTS[worker_n]=$script
|
|
2121
|
+
active_workers=$((active_workers + 1))
|
|
2122
|
+
done
|
|
2123
|
+
while [ "$active_workers" -gt 0 ]; do
|
|
2124
|
+
wait_one_completed_job_worker
|
|
2125
|
+
done
|
|
2126
|
+
# Unproven remainder, after every concurrent worker has finished.
|
|
2127
|
+
for script in "${SERIAL_TAIL_SCRIPTS[@]+"${SERIAL_TAIL_SCRIPTS[@]}"}"; do
|
|
2128
|
+
run_one_serial "$script"
|
|
2129
|
+
done
|
|
2130
|
+
fi
|
|
2131
|
+
|
|
2132
|
+
RUN_FINISHED_ISO=$(now_iso)
|
|
2133
|
+
RUN_FINISHED_MS=$(now_ms)
|
|
2134
|
+
RUN_DURATION=$((RUN_FINISHED_MS - RUN_STARTED_MS))
|
|
2135
|
+
if [ "$RUN_DURATION" -lt 0 ]; then
|
|
2136
|
+
RUN_DURATION=0
|
|
2137
|
+
fi
|
|
2138
|
+
|
|
2139
|
+
printf 'FM_TEST_SUMMARY total=%s failed=%s skipped_gate=%s duration_ms=%s\n' \
|
|
2140
|
+
"$TOTAL" "$FAILED" "$SKIPPED_GATE" "$RUN_DURATION"
|
|
2141
|
+
|
|
2142
|
+
if [ -s "$FAMILIES_TSV" ]; then
|
|
2143
|
+
# Stable family summary order by name.
|
|
2144
|
+
sort -t$'\t' -k1,1 "$FAMILIES_TSV" | while IFS=$'\t' read -r name count duration failed_count; do
|
|
2145
|
+
printf 'FM_TEST_SUMMARY_FAMILY family=%s count=%s duration_ms=%s failed=%s\n' \
|
|
2146
|
+
"$name" "$count" "$duration" "$failed_count"
|
|
2147
|
+
done
|
|
2148
|
+
fi
|
|
2149
|
+
|
|
2150
|
+
# Slowest scripts (top 15) from records.
|
|
2151
|
+
if [ -s "$RECORDS" ]; then
|
|
2152
|
+
rank=1
|
|
2153
|
+
sort -t$'\t' -k5,5nr "$RECORDS" | head -n 15 | while IFS=$'\t' read -r path _family _expected _rc duration _gate; do
|
|
2154
|
+
printf 'FM_TEST_SLOWEST rank=%s script=%s duration_ms=%s\n' \
|
|
2155
|
+
"$rank" "$path" "$duration"
|
|
2156
|
+
rank=$((rank + 1))
|
|
2157
|
+
done
|
|
2158
|
+
fi
|
|
2159
|
+
|
|
2160
|
+
if [ -n "$JSON_PATH" ]; then
|
|
2161
|
+
mkdir -p "$(dirname "$JSON_PATH")"
|
|
2162
|
+
# Families file may be unsorted; write_json reads as-is (deterministic sort in python).
|
|
2163
|
+
if [ -s "$FAMILIES_TSV" ]; then
|
|
2164
|
+
sort -t$'\t' -k1,1 "$FAMILIES_TSV" -o "$FAMILIES_TSV"
|
|
2165
|
+
else
|
|
2166
|
+
: >"$FAMILIES_TSV"
|
|
2167
|
+
fi
|
|
2168
|
+
set +e
|
|
2169
|
+
write_json_artifact "$JSON_PATH" \
|
|
2170
|
+
"$RUN_STARTED_ISO" "$RUN_FINISHED_ISO" "$RUN_ID" \
|
|
2171
|
+
"$TOTAL" "$FAILED" "$SKIPPED_GATE" "$RUN_DURATION" \
|
|
2172
|
+
"$SELECTION_DESC" "$RECORDS" "$FAMILIES_TSV"
|
|
2173
|
+
json_rc=$?
|
|
2174
|
+
set -e
|
|
2175
|
+
if [ "$json_rc" -eq 0 ]; then
|
|
2176
|
+
log "wrote timing artifact: $JSON_PATH"
|
|
2177
|
+
else
|
|
2178
|
+
log "timing artifact finalization failed: $JSON_PATH"
|
|
2179
|
+
AGG_RC=1
|
|
2180
|
+
fi
|
|
2181
|
+
fi
|
|
2182
|
+
|
|
2183
|
+
if [ -n "$MAX_WALL_MS" ]; then
|
|
2184
|
+
printf 'FM_TEST_BUDGET max_wall_ms=%s duration_ms=%s\n' "$MAX_WALL_MS" "$RUN_DURATION"
|
|
2185
|
+
if [ "$RUN_DURATION" -gt "$MAX_WALL_MS" ]; then
|
|
2186
|
+
log "wall-clock budget exceeded: ${RUN_DURATION}ms > ${MAX_WALL_MS}ms for $SELECTION_DESC"
|
|
2187
|
+
AGG_RC=1
|
|
2188
|
+
fi
|
|
2189
|
+
fi
|
|
2190
|
+
|
|
2191
|
+
exit "$AGG_RC"
|