@zerwiz/ymir 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -59
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Behavior tests for both native session-open tiers: the nudge wrapper that
|
|
3
|
+
# only asks the agent to take the helm, and the run wrapper that takes it.
|
|
4
|
+
#
|
|
5
|
+
# The run-wrapper cases drive the REAL bin/fm-session-start.sh against a
|
|
6
|
+
# throwaway home, so they prove routing by the digest that actually appears,
|
|
7
|
+
# not by inspecting the wrapper's source. docs/sessionstart-nudge.md owns the
|
|
8
|
+
# tier assignment and the source table these pin.
|
|
9
|
+
set -u
|
|
10
|
+
|
|
11
|
+
# Run the whole suite beneath one long-lived fixture harness, matching the real
|
|
12
|
+
# lifecycle in which startup and later clear/compact hooks share one harness
|
|
13
|
+
# ancestor. This also prevents a developer's ambient harness from making the
|
|
14
|
+
# portable regression pass locally while failing on a harness-free CI runner.
|
|
15
|
+
if [ "${FM_SESSIONSTART_TEST_HARNESS:-0}" != 1 ]; then
|
|
16
|
+
HARNESS_FIXTURE=$(mktemp -d "${TMPDIR:-/tmp}/fm-sessionstart-harness.XXXXXX") || exit 1
|
|
17
|
+
ln -s /bin/bash "$HARNESS_FIXTURE/codex" || exit 1
|
|
18
|
+
# shellcheck disable=SC2016 # Expand in the fixture shell, not this parent.
|
|
19
|
+
FM_SESSIONSTART_TEST_HARNESS=1 "$HARNESS_FIXTURE/codex" \
|
|
20
|
+
-c '"$@"; rc=$?; :; exit "$rc"' _ "$0" "$@"
|
|
21
|
+
HARNESS_STATUS=$?
|
|
22
|
+
rm -rf "$HARNESS_FIXTURE"
|
|
23
|
+
exit "$HARNESS_STATUS"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# shellcheck source=tests/lib.sh
|
|
27
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
|
28
|
+
|
|
29
|
+
unset NO_MISTAKES_GATE
|
|
30
|
+
|
|
31
|
+
TMP_ROOT=$(fm_test_tmproot fm-sessionstart-nudge)
|
|
32
|
+
NUDGE="$ROOT/bin/fm-sessionstart-nudge.sh"
|
|
33
|
+
RUN="$ROOT/bin/fm-sessionstart-run.sh"
|
|
34
|
+
# shellcheck source=/dev/null
|
|
35
|
+
. "$ROOT/bin/fm-operational-input.sh"
|
|
36
|
+
NUDGE_TEXT="Run \`bin/fm-session-start.sh\` now, exactly once, before executing any other instructions."
|
|
37
|
+
fm_operational_input_encode session-start "$NUDGE_TEXT" NUDGE_LINE \
|
|
38
|
+
|| fail "could not construct expected session-start nudge"
|
|
39
|
+
fm_git_identity fmtest fmtest@example.invalid
|
|
40
|
+
|
|
41
|
+
make_primary() {
|
|
42
|
+
local dir=$1
|
|
43
|
+
mkdir -p "$dir/bin" "$dir/state"
|
|
44
|
+
git init -q "$dir"
|
|
45
|
+
git -C "$dir" commit -q --allow-empty -m init
|
|
46
|
+
: > "$dir/AGENTS.md"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
run_nudge() {
|
|
50
|
+
local root=$1
|
|
51
|
+
FM_GATE_REFUSE_BYPASS=0 FM_ROOT_OVERRIDE="$root" FM_HOME="$root" "$NUDGE"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
expect_silent_zero() {
|
|
55
|
+
local label=$1
|
|
56
|
+
shift
|
|
57
|
+
local out status=0
|
|
58
|
+
out=$("$@" 2>&1) || status=$?
|
|
59
|
+
expect_code 0 "$status" "$label must exit 0"
|
|
60
|
+
[ -z "$out" ] || fail "$label must be silent, got: $out"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test_genuine_primary_nudges() {
|
|
64
|
+
local root="$TMP_ROOT/primary" out prefix_hex status=0
|
|
65
|
+
make_primary "$root"
|
|
66
|
+
out=$(run_nudge "$root") || status=$?
|
|
67
|
+
expect_code 0 "$status" "genuine primary nudge"
|
|
68
|
+
[ "$out" = "$NUDGE_LINE" ] || fail "genuine primary printed unexpected output: $out"
|
|
69
|
+
prefix_hex=$(printf '%s' "$out" | head -c 3 | od -An -tx1 | tr -d ' \n')
|
|
70
|
+
[ "$prefix_hex" = e281a3 ] || fail "genuine primary nudge lost its U+2063 operational marker: $prefix_hex"
|
|
71
|
+
pass "fm-sessionstart-nudge: a genuine primary gets one explicitly marked instruction line"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
test_gate_env_is_silent() {
|
|
75
|
+
local root="$TMP_ROOT/gate-env"
|
|
76
|
+
make_primary "$root"
|
|
77
|
+
expect_silent_zero "gate env nudge" env NO_MISTAKES_GATE=1 FM_GATE_REFUSE_BYPASS=0 \
|
|
78
|
+
FM_ROOT_OVERRIDE="$root" FM_HOME="$root" "$NUDGE"
|
|
79
|
+
pass "fm-sessionstart-nudge: NO_MISTAKES_GATE is silent"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test_gate_common_dir_is_silent() {
|
|
83
|
+
local source="$TMP_ROOT/gate-source" bare="$TMP_ROOT/.no-mistakes/repos/gate.git"
|
|
84
|
+
local root="$TMP_ROOT/gate-worktree"
|
|
85
|
+
fm_git_init_commit "$source"
|
|
86
|
+
mkdir -p "$(dirname "$bare")"
|
|
87
|
+
git clone --quiet --bare "$source" "$bare"
|
|
88
|
+
git --git-dir="$bare" worktree add --quiet -b gate-test "$root" HEAD
|
|
89
|
+
mkdir -p "$root/bin" "$root/state"
|
|
90
|
+
: > "$root/AGENTS.md"
|
|
91
|
+
printf 'gate-test\n' > "$root/.fm-secondmate-home"
|
|
92
|
+
expect_silent_zero "gate common-dir nudge" env FM_GATE_REFUSE_BYPASS=0 \
|
|
93
|
+
FM_ROOT_OVERRIDE="$root" FM_HOME="$root" "$NUDGE"
|
|
94
|
+
pass "fm-sessionstart-nudge: .no-mistakes gate common-dir is silent"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
test_unmarked_linked_worktree_is_silent() {
|
|
98
|
+
local base="$TMP_ROOT/worktree-base" root="$TMP_ROOT/worktree-child"
|
|
99
|
+
fm_git_worktree "$base" "$root" fm/sessionstart-linked
|
|
100
|
+
mkdir -p "$root/bin" "$root/state"
|
|
101
|
+
: > "$root/AGENTS.md"
|
|
102
|
+
expect_silent_zero "linked worktree nudge" run_nudge "$root"
|
|
103
|
+
pass "fm-sessionstart-nudge: an unmarked linked task worktree is silent"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
test_linked_secondmate_primary_nudges() {
|
|
107
|
+
local base="$TMP_ROOT/secondmate-base" root="$TMP_ROOT/secondmate-home" out status=0
|
|
108
|
+
fm_git_worktree "$base" "$root" fm/sessionstart-secondmate
|
|
109
|
+
mkdir -p "$root/bin" "$root/state"
|
|
110
|
+
: > "$root/AGENTS.md"
|
|
111
|
+
printf 'sessionstart-sm\n' > "$root/.fm-secondmate-home"
|
|
112
|
+
out=$(run_nudge "$root") || status=$?
|
|
113
|
+
expect_code 0 "$status" "linked secondmate nudge"
|
|
114
|
+
[ "$out" = "$NUDGE_LINE" ] || fail "linked secondmate printed unexpected output: $out"
|
|
115
|
+
pass "fm-sessionstart-nudge: a marked linked secondmate home is a primary"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
test_missing_state_is_silent() {
|
|
119
|
+
local root="$TMP_ROOT/missing-state"
|
|
120
|
+
make_primary "$root"
|
|
121
|
+
rmdir "$root/state"
|
|
122
|
+
expect_silent_zero "missing state nudge" run_nudge "$root"
|
|
123
|
+
pass "fm-sessionstart-nudge: a checkout without state is silent"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
test_owned_lock_is_silent() {
|
|
127
|
+
local root="$TMP_ROOT/already-ran"
|
|
128
|
+
make_primary "$root"
|
|
129
|
+
printf '%s\n' "$$" > "$root/state/.lock"
|
|
130
|
+
expect_silent_zero "owned lock nudge" run_nudge "$root"
|
|
131
|
+
pass "fm-sessionstart-nudge: a lock holder in process ancestry is already run"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
test_opencode_plugin_delivers_exact_nudge_once() {
|
|
135
|
+
local root="$TMP_ROOT/opencode-primary" out status=0
|
|
136
|
+
make_primary "$root"
|
|
137
|
+
cp "$ROOT/bin/fm-sessionstart-nudge.sh" "$ROOT/bin/fm-primary-scope-lib.sh" \
|
|
138
|
+
"$ROOT/bin/fm-gate-refuse-lib.sh" "$ROOT/bin/fm-operational-input.sh" "$root/bin/"
|
|
139
|
+
chmod +x "$root/bin/fm-sessionstart-nudge.sh"
|
|
140
|
+
out=$(PLUGIN="$ROOT/.opencode/plugins/fm-primary-sessionstart-nudge.js" \
|
|
141
|
+
WORKTREE="$root" EXPECTED="$NUDGE_LINE" node --input-type=module 2>&1 <<'EOF'
|
|
142
|
+
import { pathToFileURL } from "node:url";
|
|
143
|
+
|
|
144
|
+
const prompts = [];
|
|
145
|
+
const client = {
|
|
146
|
+
session: {
|
|
147
|
+
promptAsync: async (request) => {
|
|
148
|
+
prompts.push(request.body.parts[0].text);
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
const mod = await import(pathToFileURL(process.env.PLUGIN).href);
|
|
153
|
+
const hooks = await mod.FmPrimarySessionstartNudge({
|
|
154
|
+
client,
|
|
155
|
+
directory: process.env.WORKTREE,
|
|
156
|
+
worktree: process.env.WORKTREE,
|
|
157
|
+
});
|
|
158
|
+
const event = {
|
|
159
|
+
type: "session.created",
|
|
160
|
+
properties: { sessionID: "session-nudge-test", info: { id: "session-nudge-test" } },
|
|
161
|
+
};
|
|
162
|
+
await hooks.event({ event });
|
|
163
|
+
await hooks.event({ event });
|
|
164
|
+
if (prompts.length !== 1) throw new Error(`expected one prompt, got ${prompts.length}`);
|
|
165
|
+
if (prompts[0] !== process.env.EXPECTED) throw new Error(`unexpected prompt: ${prompts[0]}`);
|
|
166
|
+
EOF
|
|
167
|
+
) || status=$?
|
|
168
|
+
expect_code 0 "$status" "OpenCode exact nudge delivery"
|
|
169
|
+
[ -z "$out" ] || fail "OpenCode exact nudge delivery printed output: $out"
|
|
170
|
+
pass "OpenCode session.created delivers the exact wrapper nudge once per session"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# --- run tier ----------------------------------------------------------------
|
|
174
|
+
#
|
|
175
|
+
# make_run_primary builds a primary the run wrapper accepts and the REAL
|
|
176
|
+
# fm-session-start.sh can execute: a git repo on main so the tangle check
|
|
177
|
+
# behaves, plus the home directories the digest reads. The deliberately bare
|
|
178
|
+
# PATH keeps every bootstrap probe fast and hermetic - it reports missing tools
|
|
179
|
+
# instead of reaching the host's real gh/tmux/tasks-axi.
|
|
180
|
+
RUN_PATH=${FM_TEST_BASE_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}
|
|
181
|
+
|
|
182
|
+
make_run_primary() {
|
|
183
|
+
local dir=$1
|
|
184
|
+
mkdir -p "$dir/bin" "$dir/state" "$dir/data" "$dir/config"
|
|
185
|
+
git init -q -b main "$dir"
|
|
186
|
+
git -C "$dir" commit -q --allow-empty -m init
|
|
187
|
+
: > "$dir/AGENTS.md"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
run_hook() { # <root> [args...]
|
|
191
|
+
local root=$1
|
|
192
|
+
shift
|
|
193
|
+
env -u CLAUDECODE -u PI_CODING_AGENT -u FM_PI_HARNESS -u GROK_AGENT \
|
|
194
|
+
FM_GATE_REFUSE_BYPASS=0 FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" "$RUN" "$@"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
run_hook_pi() { # <root> [args...]
|
|
198
|
+
local root=$1
|
|
199
|
+
shift
|
|
200
|
+
env -u CLAUDECODE -u GROK_AGENT PI_CODING_AGENT=true FM_PI_HARNESS=pi \
|
|
201
|
+
FM_GATE_REFUSE_BYPASS=0 FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" "$RUN" "$@"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Every run-tier assertion keys off the digest banner, which fm-session-start.sh
|
|
205
|
+
# prints before the lock result, so routing is proven whether or not the lock
|
|
206
|
+
# was won in the test environment.
|
|
207
|
+
FULL_BANNER="SESSION START - "
|
|
208
|
+
REEMIT_BANNER="SESSION START (CONTEXT RE-EMIT) - "
|
|
209
|
+
|
|
210
|
+
test_run_startup_runs_the_full_digest() {
|
|
211
|
+
local root="$TMP_ROOT/run-startup" out status=0
|
|
212
|
+
make_run_primary "$root"
|
|
213
|
+
out=$(run_hook "$root" --source startup </dev/null) || status=$?
|
|
214
|
+
expect_code 0 "$status" "run wrapper startup"
|
|
215
|
+
assert_contains "$out" "$FULL_BANNER$root" "startup did not run the full digest"
|
|
216
|
+
assert_contains "$out" "lock acquired: harness pid" \
|
|
217
|
+
"the portable startup fixture did not supply a real harness process"
|
|
218
|
+
assert_not_contains "$out" "$REEMIT_BANNER" "startup was misrouted to a context re-emit"
|
|
219
|
+
assert_not_contains "$out" "FIRSTMATE_OP" "a run-tier open also emitted the nudge instruction"
|
|
220
|
+
assert_contains "$out" "NEXT STEP" "the run wrapper did not deliver a complete digest"
|
|
221
|
+
pass "run wrapper: startup runs the full digest and never also nudges"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
test_run_clear_and_compact_reemit() {
|
|
225
|
+
local root out source status
|
|
226
|
+
for source in clear compact; do
|
|
227
|
+
root="$TMP_ROOT/run-$source"
|
|
228
|
+
make_run_primary "$root"
|
|
229
|
+
run_hook "$root" --source startup </dev/null >/dev/null
|
|
230
|
+
assert_present "$root/state/.session-start-complete" \
|
|
231
|
+
"startup did not publish the completion proof needed by $source"
|
|
232
|
+
status=0
|
|
233
|
+
out=$(run_hook "$root" --source "$source" </dev/null) || status=$?
|
|
234
|
+
expect_code 0 "$status" "run wrapper $source"
|
|
235
|
+
assert_contains "$out" "$REEMIT_BANNER$root" "$source did not re-emit the digest"
|
|
236
|
+
assert_contains "$out" "are NOT repeated" "$source did not report the skipped startup sweeps"
|
|
237
|
+
assert_contains "$out" "Queued wakes ARE still drained" "$source did not preserve the wake-queue drain"
|
|
238
|
+
assert_not_contains "$out" "FIRSTMATE_OP" "a $source open also emitted the nudge instruction"
|
|
239
|
+
done
|
|
240
|
+
pass "run wrapper: clear and compact re-emit the digest without repeating startup sweeps"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
test_run_rebuild_forwards_source_to_drifted_instruction_refresh() {
|
|
244
|
+
local root="$TMP_ROOT/run-instruction-refresh" baseline compact_out clear_out resume_out
|
|
245
|
+
make_run_primary "$root"
|
|
246
|
+
printf '%s\n' 'RUN_TIER_AGENTS=original' > "$root/AGENTS.md"
|
|
247
|
+
run_hook_pi "$root" --source startup </dev/null >/dev/null
|
|
248
|
+
assert_present "$root/state/.session-start-agents-baseline" \
|
|
249
|
+
"run-tier startup did not record an instruction baseline"
|
|
250
|
+
baseline=$(cat "$root/state/.session-start-agents-baseline")
|
|
251
|
+
|
|
252
|
+
printf '%s\n' 'RUN_TIER_AGENTS=updated' > "$root/AGENTS.md"
|
|
253
|
+
compact_out=$(run_hook_pi "$root" --source compact </dev/null)
|
|
254
|
+
clear_out=$(run_hook_pi "$root" --source clear </dev/null)
|
|
255
|
+
resume_out=$(run_hook_pi "$root" --source resume </dev/null)
|
|
256
|
+
|
|
257
|
+
assert_contains "$compact_out" "RUN_TIER_AGENTS=updated" \
|
|
258
|
+
"the compact run wrapper did not forward its source to instruction refresh"
|
|
259
|
+
assert_not_contains "$clear_out" "CURRENT AGENTS.md - INSTRUCTION REFRESH" \
|
|
260
|
+
"the clear run wrapper emitted a replacement contract despite Pi's fresh runtime"
|
|
261
|
+
[ "$baseline" = "$(cat "$root/state/.session-start-agents-baseline")" ] \
|
|
262
|
+
|| fail "a run-tier rebuild rewrote the true-start instruction baseline"
|
|
263
|
+
[ -z "$resume_out" ] \
|
|
264
|
+
|| fail "an already-owned resume should preserve context without re-running the digest"
|
|
265
|
+
|
|
266
|
+
pass "run wrapper forwards only stale-cache rebuild sources to immutable-baseline instruction refresh"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
test_run_compact_without_completion_refreshes_before_finishing_startup() {
|
|
270
|
+
local root="$TMP_ROOT/run-compact-incomplete" out status=0 refresh_line bootstrap_line
|
|
271
|
+
make_run_primary "$root"
|
|
272
|
+
printf '%s\n' 'INCOMPLETE_START_AGENTS=current' > "$root/AGENTS.md"
|
|
273
|
+
|
|
274
|
+
out=$(run_hook_pi "$root" --source compact </dev/null) || status=$?
|
|
275
|
+
expect_code 0 "$status" "run wrapper compact without completion proof"
|
|
276
|
+
assert_contains "$out" "$FULL_BANNER$root" \
|
|
277
|
+
"compact skipped full startup when no completed startup could be proven"
|
|
278
|
+
assert_contains "$out" "INCOMPLETE_START_AGENTS=current" \
|
|
279
|
+
"compact after an incomplete startup did not conservatively inject current instructions"
|
|
280
|
+
refresh_line=$(printf '%s\n' "$out" | grep -n '^CURRENT AGENTS.md - INSTRUCTION REFRESH$' | head -1 | cut -d: -f1)
|
|
281
|
+
bootstrap_line=$(printf '%s\n' "$out" | grep -n '^BOOTSTRAP$' | head -1 | cut -d: -f1)
|
|
282
|
+
[ -n "$refresh_line" ] && [ -n "$bootstrap_line" ] && [ "$refresh_line" -lt "$bootstrap_line" ] \
|
|
283
|
+
|| fail "compact recovery did not emit current instructions before the bulky digest"
|
|
284
|
+
assert_absent "$root/state/.session-start-agents-baseline" \
|
|
285
|
+
"compact recovery fabricated a true-start instruction baseline"
|
|
286
|
+
|
|
287
|
+
pass "run wrapper refreshes a compact even when startup completion is unproven"
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
test_run_clear_without_completion_finishes_startup() {
|
|
291
|
+
local root="$TMP_ROOT/run-clear-incomplete" out status=0
|
|
292
|
+
make_run_primary "$root"
|
|
293
|
+
out=$(run_hook "$root" --source clear </dev/null) || status=$?
|
|
294
|
+
expect_code 0 "$status" "run wrapper clear without completion proof"
|
|
295
|
+
assert_contains "$out" "$FULL_BANNER$root" \
|
|
296
|
+
"clear skipped full startup when no completed startup could be proven"
|
|
297
|
+
assert_not_contains "$out" "$REEMIT_BANNER" \
|
|
298
|
+
"clear trusted lock ownership as proof that startup completed"
|
|
299
|
+
assert_present "$root/state/.session-start-complete" \
|
|
300
|
+
"the recovery full startup did not publish completion proof"
|
|
301
|
+
pass "run wrapper: clear falls back to full startup when completion is unproven"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
test_run_clear_rejects_previous_owner_completion() {
|
|
305
|
+
local root="$TMP_ROOT/run-clear-previous-owner" out status=0 previous_pid
|
|
306
|
+
make_run_primary "$root"
|
|
307
|
+
sleep 0 &
|
|
308
|
+
previous_pid=$!
|
|
309
|
+
wait "$previous_pid"
|
|
310
|
+
printf '%s\n' "$previous_pid" > "$root/state/.lock"
|
|
311
|
+
printf '%s\n' "$previous_pid" > "$root/state/.session-start-complete"
|
|
312
|
+
|
|
313
|
+
out=$(run_hook "$root" --source clear </dev/null) || status=$?
|
|
314
|
+
expect_code 0 "$status" "run wrapper clear with previous owner completion"
|
|
315
|
+
assert_contains "$out" "$FULL_BANNER$root" \
|
|
316
|
+
"clear treated a previous session's completion as current"
|
|
317
|
+
assert_not_contains "$out" "$REEMIT_BANNER" \
|
|
318
|
+
"clear skipped startup sweeps completed only by a previous session"
|
|
319
|
+
[ "$(cat "$root/state/.lock")" != "$previous_pid" ] \
|
|
320
|
+
|| fail "the recovery startup did not replace the previous session's stale lock"
|
|
321
|
+
pass "run wrapper: clear accepts completion only from the current harness"
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
test_pi_startup_classifies_cli_continuations() {
|
|
325
|
+
local fixture out expected actual status=0
|
|
326
|
+
command -v node >/dev/null 2>&1 || {
|
|
327
|
+
echo "skip: node not found for Pi continuation classification test"
|
|
328
|
+
return 0
|
|
329
|
+
}
|
|
330
|
+
fixture="$TMP_ROOT/pi-continuation-source"
|
|
331
|
+
mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state"
|
|
332
|
+
cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
|
|
333
|
+
cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
|
|
334
|
+
"$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
|
|
335
|
+
cat > "$fixture/bin/fm-sessionstart-run.sh" <<'SH'
|
|
336
|
+
#!/usr/bin/env bash
|
|
337
|
+
source_name=
|
|
338
|
+
while [ $# -gt 0 ]; do
|
|
339
|
+
case "$1" in
|
|
340
|
+
--source) source_name=${2:-}; shift 2 ;;
|
|
341
|
+
*) shift ;;
|
|
342
|
+
esac
|
|
343
|
+
done
|
|
344
|
+
printf '%s\n' "$source_name" >> "${FM_HOME:?}/state/sources"
|
|
345
|
+
SH
|
|
346
|
+
cat > "$fixture/bin/fm-turnend-guard.sh" <<'SH'
|
|
347
|
+
#!/usr/bin/env bash
|
|
348
|
+
exit 0
|
|
349
|
+
SH
|
|
350
|
+
chmod +x "$fixture/bin/"*.sh
|
|
351
|
+
|
|
352
|
+
out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
|
|
353
|
+
FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" \
|
|
354
|
+
node --input-type=module 2>&1 <<'JS'
|
|
355
|
+
import { pathToFileURL } from "node:url";
|
|
356
|
+
const handlers = new Map();
|
|
357
|
+
const pi = {
|
|
358
|
+
on(event, handler) { handlers.set(event, handler); },
|
|
359
|
+
sendMessage() {},
|
|
360
|
+
};
|
|
361
|
+
const extension = await import(`${pathToFileURL(process.env.EXT).href}?continuation=${Date.now()}`);
|
|
362
|
+
extension.default(pi);
|
|
363
|
+
let sessionNumber = 0;
|
|
364
|
+
const fire = async (args, entries = [], timestamp = new Date().toISOString()) => {
|
|
365
|
+
process.argv.splice(1, process.argv.length, "pi", ...args);
|
|
366
|
+
const sessionId = `continuation-${++sessionNumber}`;
|
|
367
|
+
const ctx = {
|
|
368
|
+
sessionManager: {
|
|
369
|
+
getEntries: () => entries,
|
|
370
|
+
getHeader: () => ({ timestamp }),
|
|
371
|
+
getSessionId: () => sessionId,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
handlers.get("session_start")({ reason: "startup" }, ctx);
|
|
375
|
+
await handlers.get("before_agent_start")({ prompt: "test" }, ctx);
|
|
376
|
+
};
|
|
377
|
+
const oldTimestamp = "2000-01-01T00:00:00.000Z";
|
|
378
|
+
const nameEntry = [{ type: "session_info", name: "named" }];
|
|
379
|
+
await fire([]);
|
|
380
|
+
await fire(["-c"]);
|
|
381
|
+
await fire(["--continue"], [{ type: "message" }], oldTimestamp);
|
|
382
|
+
await fire(["--resume"]);
|
|
383
|
+
await fire(["-r"], [{ type: "message" }], oldTimestamp);
|
|
384
|
+
await fire(["--session", "new-session"]);
|
|
385
|
+
await fire(["--session=existing-session"], [{ type: "message" }], oldTimestamp);
|
|
386
|
+
await fire(["--session-id", "new-id"]);
|
|
387
|
+
await fire(["--session-id=existing-id"], [{ type: "message" }], oldTimestamp);
|
|
388
|
+
await fire(["--session-id", "empty-existing-id"], [], oldTimestamp);
|
|
389
|
+
await fire(["-c", "--name", "new-named"], nameEntry);
|
|
390
|
+
await fire(["-c", "--name", "restored-named"], nameEntry, oldTimestamp);
|
|
391
|
+
await fire(["--session-id", "new-named-id", "--name", "new-named"], nameEntry);
|
|
392
|
+
await fire(["--session", "existing-named", "--name", "restored-named"], nameEntry, oldTimestamp);
|
|
393
|
+
await fire(["--fork=session-id"]);
|
|
394
|
+
await fire([], [{ type: "message" }], oldTimestamp);
|
|
395
|
+
JS
|
|
396
|
+
) || status=$?
|
|
397
|
+
expect_code 0 "$status" "Pi continuation classification"
|
|
398
|
+
[ -z "$out" ] || fail "Pi continuation classification printed output: $out"
|
|
399
|
+
expected=$(printf '%s\n' \
|
|
400
|
+
'startup' \
|
|
401
|
+
'startup' \
|
|
402
|
+
'resume' \
|
|
403
|
+
'startup' \
|
|
404
|
+
'resume' \
|
|
405
|
+
'startup' \
|
|
406
|
+
'resume' \
|
|
407
|
+
'startup' \
|
|
408
|
+
'resume' \
|
|
409
|
+
'resume' \
|
|
410
|
+
'startup' \
|
|
411
|
+
'resume' \
|
|
412
|
+
'startup' \
|
|
413
|
+
'resume' \
|
|
414
|
+
'fork' \
|
|
415
|
+
'startup')
|
|
416
|
+
actual=$(cat "$fixture/state/sources")
|
|
417
|
+
[ "$actual" = "$expected" ] \
|
|
418
|
+
|| fail "Pi continuation classification produced unexpected sources: $actual"
|
|
419
|
+
pass "Pi distinguishes header-proven restored CLI sessions from named create-if-missing startups"
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
test_pi_sessionstart_generation_prerequisite() {
|
|
423
|
+
local fixture out status=0
|
|
424
|
+
command -v node >/dev/null 2>&1 || {
|
|
425
|
+
echo "skip: node not found for Pi session-start generation prerequisite test"
|
|
426
|
+
return 0
|
|
427
|
+
}
|
|
428
|
+
fixture="$TMP_ROOT/pi-sessionstart-generation"
|
|
429
|
+
mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state"
|
|
430
|
+
cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
|
|
431
|
+
cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
|
|
432
|
+
"$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
|
|
433
|
+
cp "$ROOT/bin/fm-operational-input.sh" "$fixture/bin/"
|
|
434
|
+
cat > "$fixture/bin/fm-turnend-guard.sh" <<'SH'
|
|
435
|
+
#!/usr/bin/env bash
|
|
436
|
+
exit 0
|
|
437
|
+
SH
|
|
438
|
+
cat > "$fixture/bin/fm-sessionstart-run.sh" <<'SH'
|
|
439
|
+
#!/usr/bin/env bash
|
|
440
|
+
set -u
|
|
441
|
+
state=${FM_HOME:?}/state
|
|
442
|
+
source_name=
|
|
443
|
+
while [ $# -gt 0 ]; do
|
|
444
|
+
case "$1" in
|
|
445
|
+
--source) source_name=${2:-}; shift 2 ;;
|
|
446
|
+
*) shift ;;
|
|
447
|
+
esac
|
|
448
|
+
done
|
|
449
|
+
count=$(( $(wc -l < "$state/launches" 2>/dev/null || printf '0') + 1 ))
|
|
450
|
+
behavior=$(cat "$state/behavior-$count")
|
|
451
|
+
printf 'launch:%s:%s:%s:%s:%s\n' \
|
|
452
|
+
"$count" "$behavior" "$source_name" "$$" "${FM_SESSIONSTART_SUPERVISOR_PID:-}" >> "$state/launches"
|
|
453
|
+
printf 'launch:%s\n' "$count" >> "$state/events"
|
|
454
|
+
case "$behavior" in
|
|
455
|
+
success)
|
|
456
|
+
printf 'GENERATION_DIGEST_%s source=%s\n' "$count" "$source_name"
|
|
457
|
+
: > "$state/completed-$count"
|
|
458
|
+
;;
|
|
459
|
+
slow|timeout)
|
|
460
|
+
(
|
|
461
|
+
trap '' TERM INT
|
|
462
|
+
while :; do sleep 30; done
|
|
463
|
+
) </dev/null >/dev/null 2>&1 &
|
|
464
|
+
grandchild=$!
|
|
465
|
+
printf '%s\n' "$grandchild" > "$state/grandchild-$count"
|
|
466
|
+
trap 'printf "term:'"$count"'\n" >> "$state/events"; exit 143' TERM INT
|
|
467
|
+
: > "$state/started-$count"
|
|
468
|
+
while [ ! -f "$state/release-$count" ]; do sleep 0.02; done
|
|
469
|
+
if [ "$behavior" = timeout ]; then
|
|
470
|
+
printf 'STARTUP TRUNCATED - stage=bootstrap generation=%s\n' "$count"
|
|
471
|
+
else
|
|
472
|
+
printf 'GENERATION_DIGEST_%s source=%s\n' "$count" "$source_name"
|
|
473
|
+
fi
|
|
474
|
+
: > "$state/completed-$count"
|
|
475
|
+
kill -KILL "$grandchild" 2>/dev/null || true
|
|
476
|
+
wait "$grandchild" 2>/dev/null || true
|
|
477
|
+
;;
|
|
478
|
+
stale)
|
|
479
|
+
trap 'printf "stale-close:'"$count"'\n" >> "$state/events"; printf "STALE_DIGEST_'"$count"'\n"; : > "$state/completed-'"$count"'"; exit 0' TERM INT
|
|
480
|
+
: > "$state/started-$count"
|
|
481
|
+
while [ ! -f "$state/release-$count" ]; do sleep 0.02; done
|
|
482
|
+
printf 'STALE_DIGEST_%s\n' "$count"
|
|
483
|
+
: > "$state/completed-$count"
|
|
484
|
+
;;
|
|
485
|
+
empty)
|
|
486
|
+
: > "$state/completed-$count"
|
|
487
|
+
;;
|
|
488
|
+
ineligible)
|
|
489
|
+
: > "$state/completed-$count"
|
|
490
|
+
exit 3
|
|
491
|
+
;;
|
|
492
|
+
error)
|
|
493
|
+
: > "$state/completed-$count"
|
|
494
|
+
exit 7
|
|
495
|
+
;;
|
|
496
|
+
truncate)
|
|
497
|
+
printf 'TRUNCATION_PREFIX_%s\n' "$count"
|
|
498
|
+
i=0
|
|
499
|
+
while [ "$i" -lt 700 ]; do
|
|
500
|
+
printf '%01024d' 0
|
|
501
|
+
i=$((i + 1))
|
|
502
|
+
done
|
|
503
|
+
printf '\nTRUNCATION_SUFFIX_%s\n' "$count"
|
|
504
|
+
: > "$state/completed-$count"
|
|
505
|
+
;;
|
|
506
|
+
*)
|
|
507
|
+
exit 9
|
|
508
|
+
;;
|
|
509
|
+
esac
|
|
510
|
+
SH
|
|
511
|
+
chmod +x "$fixture/bin/"*.sh
|
|
512
|
+
: > "$fixture/state/launches"
|
|
513
|
+
: > "$fixture/state/events"
|
|
514
|
+
|
|
515
|
+
out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
|
|
516
|
+
FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" \
|
|
517
|
+
node --input-type=module 2>&1 <<'JS'
|
|
518
|
+
import {
|
|
519
|
+
existsSync,
|
|
520
|
+
readFileSync,
|
|
521
|
+
renameSync,
|
|
522
|
+
writeFileSync,
|
|
523
|
+
} from "node:fs";
|
|
524
|
+
import { pathToFileURL } from "node:url";
|
|
525
|
+
|
|
526
|
+
const state = `${process.env.FM_HOME}/state`;
|
|
527
|
+
const runner = `${process.env.FM_HOME}/bin/fm-sessionstart-run.sh`;
|
|
528
|
+
const handlers = new Map();
|
|
529
|
+
const sent = [];
|
|
530
|
+
const pi = {
|
|
531
|
+
on(event, handler) { handlers.set(event, handler); },
|
|
532
|
+
sendMessage(message) { sent.push(message); },
|
|
533
|
+
};
|
|
534
|
+
const extension = await import(`${pathToFileURL(process.env.EXT).href}?generation=${Date.now()}`);
|
|
535
|
+
extension.default(pi);
|
|
536
|
+
process.argv.splice(1, process.argv.length, "pi");
|
|
537
|
+
|
|
538
|
+
const assert = (condition, message) => {
|
|
539
|
+
if (!condition) throw new Error(message);
|
|
540
|
+
};
|
|
541
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
542
|
+
const waitFor = async (predicate, message) => {
|
|
543
|
+
for (let i = 0; i < 400; i += 1) {
|
|
544
|
+
if (predicate()) return;
|
|
545
|
+
await delay(5);
|
|
546
|
+
}
|
|
547
|
+
throw new Error(message);
|
|
548
|
+
};
|
|
549
|
+
const alive = (pid) => {
|
|
550
|
+
try { process.kill(Number(pid), 0); return true; } catch { return false; }
|
|
551
|
+
};
|
|
552
|
+
const waitDead = async (pid, message) => {
|
|
553
|
+
await waitFor(() => !alive(pid), message);
|
|
554
|
+
};
|
|
555
|
+
const ctx = (sessionId) => ({
|
|
556
|
+
sessionManager: {
|
|
557
|
+
getHeader: () => ({ timestamp: new Date().toISOString() }),
|
|
558
|
+
getSessionId: () => sessionId,
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
const begin = (reason, sessionId) => {
|
|
562
|
+
const current = ctx(sessionId);
|
|
563
|
+
handlers.get("session_start")({ reason }, current);
|
|
564
|
+
return current;
|
|
565
|
+
};
|
|
566
|
+
const providerCalls = [];
|
|
567
|
+
const providerCall = async (current, prompt) => {
|
|
568
|
+
const preflight = await handlers.get("before_agent_start")({ prompt }, current);
|
|
569
|
+
providerCalls.push({ prompt, message: preflight?.message });
|
|
570
|
+
return preflight;
|
|
571
|
+
};
|
|
572
|
+
const plan = (index, behavior) => {
|
|
573
|
+
writeFileSync(`${state}/behavior-${index}`, `${behavior}\n`);
|
|
574
|
+
};
|
|
575
|
+
const release = (index) => writeFileSync(`${state}/release-${index}`, "\n");
|
|
576
|
+
const launchLines = () => readFileSync(`${state}/launches`, "utf8").trim().split("\n").filter(Boolean);
|
|
577
|
+
const pidFor = (index) => Number(launchLines().find((line) => line.startsWith(`launch:${index}:`))?.split(":")[4]);
|
|
578
|
+
const supervisorFor = (index) => Number(launchLines().find((line) => line.startsWith(`launch:${index}:`))?.split(":")[5]);
|
|
579
|
+
const grandchildFor = (index) => Number(readFileSync(`${state}/grandchild-${index}`, "utf8").trim());
|
|
580
|
+
const startupMessages = () => providerCalls.map((call) => call.message).filter(Boolean);
|
|
581
|
+
|
|
582
|
+
// The failing immediate-prompt path is now a prerequisite: no provider call is
|
|
583
|
+
// observable until the matching slow native generation settles.
|
|
584
|
+
plan(1, "slow");
|
|
585
|
+
const immediate = begin("startup", "session-immediate");
|
|
586
|
+
await waitFor(() => existsSync(`${state}/started-1`), "immediate generation never started");
|
|
587
|
+
const immediateCall = providerCall(immediate, "immediate prompt");
|
|
588
|
+
await delay(100);
|
|
589
|
+
assert(providerCalls.length === 0, "provider call escaped before the startup prerequisite settled");
|
|
590
|
+
release(1);
|
|
591
|
+
const immediateResult = await immediateCall;
|
|
592
|
+
assert(immediateResult?.message?.content.includes("GENERATION_DIGEST_1"), "first payload lost matching startup context");
|
|
593
|
+
assert(launchLines().length === 1, "immediate generation executed more than once");
|
|
594
|
+
assert((await handlers.get("before_agent_start")({ prompt: "second prompt" }, immediate)) === undefined,
|
|
595
|
+
"one generation delivered startup context more than once");
|
|
596
|
+
assert(startupMessages().length === 1, "immediate generation produced more than one model-visible startup context");
|
|
597
|
+
assert(sent.length === 0, "session start still used asynchronous sendMessage delivery");
|
|
598
|
+
|
|
599
|
+
// Proven non-racing path: native completion before submission produces the same
|
|
600
|
+
// first-payload guarantee without a manual fallback.
|
|
601
|
+
plan(2, "success");
|
|
602
|
+
const proven = begin("new", "session-proven");
|
|
603
|
+
await waitFor(() => existsSync(`${state}/completed-2`), "proven generation never completed");
|
|
604
|
+
const provenResult = await providerCall(proven, "proven prompt");
|
|
605
|
+
assert(provenResult?.message?.content.includes("GENERATION_DIGEST_2"), "proven path lost startup context");
|
|
606
|
+
assert(!provenResult.message.content.includes("Run `bin/fm-session-start.sh`"), "proven path used manual fallback");
|
|
607
|
+
const provenSupervisor = supervisorFor(2);
|
|
608
|
+
assert(alive(provenSupervisor), "completed generation lost its stable supervisor owner");
|
|
609
|
+
const originalKill = process.kill;
|
|
610
|
+
let ownedGroupSignalCount = 0;
|
|
611
|
+
process.kill = (pid, signal) => {
|
|
612
|
+
if (pid === -provenSupervisor && signal !== 0) ownedGroupSignalCount += 1;
|
|
613
|
+
return Reflect.apply(originalKill, process, [pid, signal]);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// Shutdown owns interruption and the whole child process group. The pending
|
|
617
|
+
// preflight settles without stale delivery.
|
|
618
|
+
plan(3, "slow");
|
|
619
|
+
const interrupted = begin("new", "session-interrupted");
|
|
620
|
+
process.kill = originalKill;
|
|
621
|
+
assert(ownedGroupSignalCount > 0, "replacement did not retire the completed generation's stable owner");
|
|
622
|
+
await waitFor(() => existsSync(`${state}/started-3`) && existsSync(`${state}/grandchild-3`),
|
|
623
|
+
"interrupted generation never started its process tree");
|
|
624
|
+
const interruptedCall = handlers.get("before_agent_start")({ prompt: "interrupted" }, interrupted);
|
|
625
|
+
const interruptedPid = pidFor(3);
|
|
626
|
+
const interruptedGrandchild = grandchildFor(3);
|
|
627
|
+
await handlers.get("session_shutdown")({ reason: "new" }, interrupted);
|
|
628
|
+
assert((await interruptedCall) === undefined, "shutdown delivered cancelled startup context");
|
|
629
|
+
await waitDead(interruptedPid, "shutdown left the startup child alive");
|
|
630
|
+
await waitDead(interruptedGrandchild, "shutdown left a startup grandchild alive");
|
|
631
|
+
|
|
632
|
+
// Two rapid replacements serialize retirement. The middle generation never
|
|
633
|
+
// starts, stale completion is ignored, and only the newest generation delivers.
|
|
634
|
+
plan(4, "stale");
|
|
635
|
+
plan(5, "success");
|
|
636
|
+
const stale = begin("new", "session-stale");
|
|
637
|
+
await waitFor(() => existsSync(`${state}/started-4`), "stale generation never started");
|
|
638
|
+
const staleCall = handlers.get("before_agent_start")({ prompt: "stale" }, stale);
|
|
639
|
+
begin("new", "session-replaced-once");
|
|
640
|
+
const newest = begin("new", "session-replaced-twice");
|
|
641
|
+
const newestResult = await providerCall(newest, "newest");
|
|
642
|
+
assert(newestResult?.message?.content.includes("GENERATION_DIGEST_5"), "newest replacement lost its context");
|
|
643
|
+
assert((await staleCall) === undefined, "stale generation delivered after replacement");
|
|
644
|
+
assert(launchLines().length === 5, "rapid replacements launched the cancelled middle generation");
|
|
645
|
+
assert(!startupMessages().some((message) => message.content.includes("STALE_DIGEST_4")),
|
|
646
|
+
"stale completion reached model context");
|
|
647
|
+
const events = readFileSync(`${state}/events`, "utf8");
|
|
648
|
+
assert(events.indexOf("stale-close:4") < events.indexOf("launch:5"),
|
|
649
|
+
"newest generation started before stale child retirement completed");
|
|
650
|
+
|
|
651
|
+
// Empty output and spawn failure settle first, then inject the existing exact
|
|
652
|
+
// manual instruction. Intentional ineligibility remains silent.
|
|
653
|
+
plan(6, "empty");
|
|
654
|
+
const empty = begin("new", "session-empty");
|
|
655
|
+
const emptyResult = await providerCall(empty, "empty");
|
|
656
|
+
assert(existsSync(`${state}/completed-6`), "empty attempt had not settled before fallback delivery");
|
|
657
|
+
assert(emptyResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once, before executing any other instructions."),
|
|
658
|
+
"empty output lost the exact manual fallback");
|
|
659
|
+
|
|
660
|
+
renameSync(runner, `${runner}.missing`);
|
|
661
|
+
const spawnError = begin("new", "session-spawn-error");
|
|
662
|
+
const spawnErrorResult = await providerCall(spawnError, "spawn error");
|
|
663
|
+
renameSync(`${runner}.missing`, runner);
|
|
664
|
+
assert(spawnErrorResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
|
|
665
|
+
"spawn error lost the manual fallback");
|
|
666
|
+
|
|
667
|
+
plan(7, "ineligible");
|
|
668
|
+
const ineligible = begin("new", "session-ineligible");
|
|
669
|
+
assert((await providerCall(ineligible, "ineligible")) === undefined,
|
|
670
|
+
"intentional gate/scope stand-down injected a manual fallback");
|
|
671
|
+
|
|
672
|
+
plan(8, "error");
|
|
673
|
+
const failed = begin("new", "session-failed");
|
|
674
|
+
const failedResult = await providerCall(failed, "failed");
|
|
675
|
+
assert(failedResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
|
|
676
|
+
"failed eligible attempt lost the manual fallback");
|
|
677
|
+
|
|
678
|
+
plan(9, "error");
|
|
679
|
+
const failedResume = begin("resume", "session-failed-resume");
|
|
680
|
+
const failedResumeResult = await providerCall(failedResume, "failed resume");
|
|
681
|
+
assert(failedResumeResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
|
|
682
|
+
"failed eligible resume lost the manual fallback");
|
|
683
|
+
|
|
684
|
+
plan(10, "error");
|
|
685
|
+
const failedFork = begin("fork", "session-failed-fork");
|
|
686
|
+
const failedForkResult = await providerCall(failedFork, "failed fork");
|
|
687
|
+
assert(failedForkResult?.message?.content.includes("Run `bin/fm-session-start.sh` now, exactly once"),
|
|
688
|
+
"failed eligible fork lost the manual fallback");
|
|
689
|
+
|
|
690
|
+
plan(11, "empty");
|
|
691
|
+
const emptyResume = begin("resume", "session-empty-resume");
|
|
692
|
+
assert((await providerCall(emptyResume, "empty resume")) === undefined,
|
|
693
|
+
"empty context-restored resume injected a manual fallback");
|
|
694
|
+
|
|
695
|
+
plan(12, "empty");
|
|
696
|
+
const emptyFork = begin("fork", "session-empty-fork");
|
|
697
|
+
assert((await providerCall(emptyFork, "empty fork")) === undefined,
|
|
698
|
+
"empty context-restored fork injected a manual fallback");
|
|
699
|
+
|
|
700
|
+
// The wrapper's bounded timeout result and the extension's 512 KiB containment
|
|
701
|
+
// remain loud provider prerequisites rather than falling back or going stale.
|
|
702
|
+
plan(13, "timeout");
|
|
703
|
+
const timed = begin("new", "session-timeout");
|
|
704
|
+
await waitFor(() => existsSync(`${state}/started-13`), "timeout generation never started");
|
|
705
|
+
const timedCall = providerCall(timed, "timeout");
|
|
706
|
+
await delay(100);
|
|
707
|
+
assert(!providerCalls.some((call) => call.prompt === "timeout"), "timeout provider call escaped before settlement");
|
|
708
|
+
release(13);
|
|
709
|
+
const timedResult = await timedCall;
|
|
710
|
+
assert(timedResult?.message?.content.includes("STARTUP TRUNCATED - stage=bootstrap"), "timeout banner was lost");
|
|
711
|
+
assert(!timedResult.message.content.includes("Run `bin/fm-session-start.sh`"), "bounded timeout incorrectly used manual fallback");
|
|
712
|
+
|
|
713
|
+
plan(14, "truncate");
|
|
714
|
+
const truncated = begin("new", "session-truncated");
|
|
715
|
+
const truncatedResult = await providerCall(truncated, "truncated");
|
|
716
|
+
assert(truncatedResult?.message?.content.includes("TRUNCATION_PREFIX_14"), "truncated digest lost its prefix");
|
|
717
|
+
assert(truncatedResult.message.content.includes("PI SESSION-START DELIVERY TRUNCATED"), "truncated digest was not loud");
|
|
718
|
+
assert(!truncatedResult.message.content.includes("TRUNCATION_SUFFIX_14"), "truncated digest exceeded its bound");
|
|
719
|
+
|
|
720
|
+
// Compaction keeps its supported asynchronous transport for retry-without-
|
|
721
|
+
// preflight, but it shares the same generation cancellation and exactly-once claim.
|
|
722
|
+
plan(15, "success");
|
|
723
|
+
const compactContext = ctx("session-truncated");
|
|
724
|
+
await handlers.get("session_compact")({}, compactContext);
|
|
725
|
+
assert(sent.length === 1 && sent[0].content.includes("GENERATION_DIGEST_15"),
|
|
726
|
+
"compaction lost its existing persistent delivery");
|
|
727
|
+
assert((await handlers.get("before_agent_start")({ prompt: "post compact" }, compactContext)) === undefined,
|
|
728
|
+
"compaction delivered the same context twice");
|
|
729
|
+
|
|
730
|
+
plan(16, "slow");
|
|
731
|
+
const compactPending = handlers.get("session_compact")({}, compactContext);
|
|
732
|
+
await waitFor(() => existsSync(`${state}/started-16`) && existsSync(`${state}/grandchild-16`),
|
|
733
|
+
"cancelled compaction generation never started");
|
|
734
|
+
const compactPid = pidFor(16);
|
|
735
|
+
const compactGrandchild = grandchildFor(16);
|
|
736
|
+
await handlers.get("session_shutdown")({ reason: "quit" }, compactContext);
|
|
737
|
+
await compactPending;
|
|
738
|
+
assert(sent.length === 1, "cancelled compaction delivered stale context");
|
|
739
|
+
await waitDead(compactPid, "shutdown left the compaction child alive");
|
|
740
|
+
await waitDead(compactGrandchild, "shutdown left a compaction grandchild alive");
|
|
741
|
+
JS
|
|
742
|
+
) || status=$?
|
|
743
|
+
expect_code 0 "$status" "Pi session-start generation prerequisite"
|
|
744
|
+
[ -z "$out" ] || fail "Pi session-start generation prerequisite printed output: $out"
|
|
745
|
+
pass "Pi provider preflight owns one generation-bound startup prerequisite with deterministic fallback, replacement, cancellation, timeout, and truncation"
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
test_pi_reload_releases_sessionstart_exit_listener() {
|
|
749
|
+
local fixture out status=0
|
|
750
|
+
command -v node >/dev/null 2>&1 || {
|
|
751
|
+
echo "skip: node not found for Pi reload exit-listener test"
|
|
752
|
+
return 0
|
|
753
|
+
}
|
|
754
|
+
fixture="$TMP_ROOT/pi-reload-exit-listener"
|
|
755
|
+
mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state"
|
|
756
|
+
cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
|
|
757
|
+
cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
|
|
758
|
+
"$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
|
|
759
|
+
cp "$ROOT/bin/fm-operational-input.sh" "$fixture/bin/"
|
|
760
|
+
cat > "$fixture/bin/fm-turnend-guard.sh" <<'SH'
|
|
761
|
+
#!/usr/bin/env bash
|
|
762
|
+
exit 0
|
|
763
|
+
SH
|
|
764
|
+
cat > "$fixture/bin/fm-sessionstart-run.sh" <<'SH'
|
|
765
|
+
#!/usr/bin/env bash
|
|
766
|
+
set -u
|
|
767
|
+
state=${FM_HOME:?}/state
|
|
768
|
+
index=$(( $(wc -l < "$state/launches" 2>/dev/null || printf '0') + 1 ))
|
|
769
|
+
printf '%s:%s:%s\n' "$index" "$$" "${FM_SESSIONSTART_SUPERVISOR_PID:-}" >> "$state/launches"
|
|
770
|
+
(
|
|
771
|
+
trap '' TERM INT HUP
|
|
772
|
+
while :; do sleep 30; done
|
|
773
|
+
) </dev/null >/dev/null 2>&1 &
|
|
774
|
+
grandchild=$!
|
|
775
|
+
printf '%s\n' "$grandchild" > "$state/grandchild-$index"
|
|
776
|
+
: > "$state/started-$index"
|
|
777
|
+
if [ "$index" -eq 3 ]; then
|
|
778
|
+
exit 0
|
|
779
|
+
fi
|
|
780
|
+
trap 'exit 143' TERM INT
|
|
781
|
+
while :; do sleep 30; done
|
|
782
|
+
SH
|
|
783
|
+
chmod +x "$fixture/bin/"*.sh
|
|
784
|
+
: > "$fixture/state/launches"
|
|
785
|
+
|
|
786
|
+
out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
|
|
787
|
+
FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" \
|
|
788
|
+
node --input-type=module 2>&1 <<'JS'
|
|
789
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
790
|
+
import { spawnSync } from "node:child_process";
|
|
791
|
+
import { pathToFileURL } from "node:url";
|
|
792
|
+
|
|
793
|
+
const state = `${process.env.FM_HOME}/state`;
|
|
794
|
+
const baselineListeners = process.listeners("exit");
|
|
795
|
+
const baselineCount = baselineListeners.length;
|
|
796
|
+
const assert = (condition, message) => {
|
|
797
|
+
if (!condition) throw new Error(message);
|
|
798
|
+
};
|
|
799
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
800
|
+
const waitFor = async (predicate, message) => {
|
|
801
|
+
for (let i = 0; i < 600; i += 1) {
|
|
802
|
+
if (predicate()) return;
|
|
803
|
+
await delay(5);
|
|
804
|
+
}
|
|
805
|
+
throw new Error(message);
|
|
806
|
+
};
|
|
807
|
+
const alive = (pid) => {
|
|
808
|
+
try { process.kill(Number(pid), 0); } catch { return false; }
|
|
809
|
+
const status = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
|
|
810
|
+
return status.status === 0 && !status.stdout.trim().startsWith("Z");
|
|
811
|
+
};
|
|
812
|
+
const ctx = (sessionId) => ({
|
|
813
|
+
sessionManager: {
|
|
814
|
+
getHeader: () => ({ timestamp: new Date().toISOString() }),
|
|
815
|
+
getSessionId: () => sessionId,
|
|
816
|
+
},
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
let launchIndex = 0;
|
|
820
|
+
for (let instance = 1; instance <= 2; instance += 1) {
|
|
821
|
+
const handlers = new Map();
|
|
822
|
+
const pi = {
|
|
823
|
+
on(event, handler) { handlers.set(event, handler); },
|
|
824
|
+
sendMessage() {},
|
|
825
|
+
};
|
|
826
|
+
const extension = await import(
|
|
827
|
+
`${pathToFileURL(process.env.EXT).href}?reload=${instance}-${Date.now()}`
|
|
828
|
+
);
|
|
829
|
+
extension.default(pi);
|
|
830
|
+
assert(process.listenerCount("exit") === baselineCount + 1,
|
|
831
|
+
`reload ${instance} did not own exactly one exit listener`);
|
|
832
|
+
const sessionCount = instance === 1 ? 2 : 1;
|
|
833
|
+
for (let session = 1; session <= sessionCount; session += 1) {
|
|
834
|
+
launchIndex += 1;
|
|
835
|
+
const current = ctx(`reload-${instance}-session-${session}`);
|
|
836
|
+
handlers.get("session_start")({ reason: "new" }, current);
|
|
837
|
+
assert(process.listenerCount("exit") === baselineCount + 1,
|
|
838
|
+
`reload ${instance} session ${session} did not own exactly one exit listener`);
|
|
839
|
+
await waitFor(() => existsSync(`${state}/started-${launchIndex}`),
|
|
840
|
+
`reload ${instance} session ${session} startup generation never started`);
|
|
841
|
+
const launch = readFileSync(`${state}/launches`, "utf8").trim().split("\n")[launchIndex - 1];
|
|
842
|
+
const child = Number(launch.split(":")[1]);
|
|
843
|
+
const supervisor = Number(launch.split(":")[2]);
|
|
844
|
+
const grandchild = Number(readFileSync(`${state}/grandchild-${launchIndex}`, "utf8").trim());
|
|
845
|
+
if (launchIndex === 3) {
|
|
846
|
+
await waitFor(() => !alive(child), "leader did not close before process-exit cleanup");
|
|
847
|
+
assert(alive(grandchild), "TERM-resistant descendant exited with its leader");
|
|
848
|
+
assert(alive(supervisor), "leader close lost the stable supervisor owner");
|
|
849
|
+
assert(process.listenerCount("exit") === baselineCount + 1,
|
|
850
|
+
"leader close removed the active instance exit listener");
|
|
851
|
+
const ownedListener = process.listeners("exit").find(
|
|
852
|
+
(listener) => !baselineListeners.includes(listener)
|
|
853
|
+
);
|
|
854
|
+
assert(ownedListener, "active reload had no process-exit cleanup listener");
|
|
855
|
+
ownedListener(0);
|
|
856
|
+
await waitFor(() => !alive(grandchild),
|
|
857
|
+
"active process-exit cleanup left the leaderless process group alive");
|
|
858
|
+
}
|
|
859
|
+
await handlers.get("session_shutdown")({ reason: "reload" }, current);
|
|
860
|
+
assert(process.listenerCount("exit") === baselineCount,
|
|
861
|
+
`reload ${instance} session ${session} retained its exit listener after shutdown`);
|
|
862
|
+
assert((await handlers.get("before_agent_start")({ prompt: "stale" }, current)) === undefined,
|
|
863
|
+
`reload ${instance} session ${session} retained model-visible startup context after shutdown`);
|
|
864
|
+
assert(!alive(child) && !alive(grandchild),
|
|
865
|
+
`reload ${instance} session ${session} retained its stale startup process group`);
|
|
866
|
+
if (session < sessionCount) {
|
|
867
|
+
assert(process.listenerCount("exit") === baselineCount,
|
|
868
|
+
"same-instance replacement started with a stale exit listener");
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
JS
|
|
873
|
+
) || status=$?
|
|
874
|
+
[ -z "$out" ] || fail "Pi reload exit-listener ownership printed output: $out"
|
|
875
|
+
expect_code 0 "$status" "Pi reload exit-listener ownership"
|
|
876
|
+
pass "Pi reload shutdown releases its exit listener and stale startup generation"
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
test_pi_large_sessionstart_digest_is_delivered_loudly() {
|
|
880
|
+
local fixture out status=0
|
|
881
|
+
command -v node >/dev/null 2>&1 || {
|
|
882
|
+
echo "skip: node not found for Pi large session-start delivery test"
|
|
883
|
+
return 0
|
|
884
|
+
}
|
|
885
|
+
fixture="$TMP_ROOT/pi-large-digest"
|
|
886
|
+
mkdir -p "$fixture/.pi/extensions/lib" "$fixture/bin" "$fixture/state" "$fixture/data" "$fixture/config"
|
|
887
|
+
git init -q -b main "$fixture"
|
|
888
|
+
git -C "$fixture" commit -q --allow-empty -m init
|
|
889
|
+
: > "$fixture/AGENTS.md"
|
|
890
|
+
cp "$ROOT/.pi/extensions/fm-primary-turnend-guard.ts" "$fixture/.pi/extensions/"
|
|
891
|
+
cp "$ROOT/.pi/extensions/lib/fm-operational-input.ts" \
|
|
892
|
+
"$ROOT/.pi/extensions/lib/fm-sessionstart-supervisor.mjs" "$fixture/.pi/extensions/lib/"
|
|
893
|
+
cp "$ROOT/bin/fm-sessionstart-run.sh" "$ROOT/bin/fm-sessionstart-nudge.sh" \
|
|
894
|
+
"$ROOT/bin/fm-primary-scope-lib.sh" "$ROOT/bin/fm-gate-refuse-lib.sh" \
|
|
895
|
+
"$ROOT/bin/fm-hook-host-lib.sh" \
|
|
896
|
+
"$ROOT/bin/fm-operational-input.sh" "$fixture/bin/"
|
|
897
|
+
cat > "$fixture/bin/fm-session-start.sh" <<'SH'
|
|
898
|
+
#!/usr/bin/env bash
|
|
899
|
+
printf 'PI_LARGE_DIGEST_PREFIX\n'
|
|
900
|
+
i=0
|
|
901
|
+
while [ "$i" -lt 700 ]; do
|
|
902
|
+
printf '%01024d' 0
|
|
903
|
+
i=$((i + 1))
|
|
904
|
+
done
|
|
905
|
+
printf '\nPI_LARGE_DIGEST_SUFFIX\n'
|
|
906
|
+
SH
|
|
907
|
+
chmod +x "$fixture/bin/"*.sh
|
|
908
|
+
|
|
909
|
+
out=$(EXT="$fixture/.pi/extensions/fm-primary-turnend-guard.ts" \
|
|
910
|
+
FM_HOME="$fixture" FM_ROOT_OVERRIDE="$fixture" FM_GATE_REFUSE_BYPASS=1 \
|
|
911
|
+
node --input-type=module 2>&1 <<'JS'
|
|
912
|
+
import { pathToFileURL } from "node:url";
|
|
913
|
+
const handlers = new Map();
|
|
914
|
+
const pi = {
|
|
915
|
+
on(event, handler) { handlers.set(event, handler); },
|
|
916
|
+
sendMessage() { throw new Error("session start must use provider preflight"); },
|
|
917
|
+
};
|
|
918
|
+
const extension = await import(`${pathToFileURL(process.env.EXT).href}?large=${Date.now()}`);
|
|
919
|
+
extension.default(pi);
|
|
920
|
+
const ctx = { sessionManager: { getEntries: () => [], getSessionId: () => "large-digest" } };
|
|
921
|
+
handlers.get("session_start")({ reason: "startup" }, ctx);
|
|
922
|
+
const result = await handlers.get("before_agent_start")({ prompt: "test" }, ctx);
|
|
923
|
+
if (!result?.message) throw new Error("expected one persistent preflight message");
|
|
924
|
+
const content = result.message.content;
|
|
925
|
+
if (!content.includes("PI_LARGE_DIGEST_PREFIX")) throw new Error("digest prefix was lost");
|
|
926
|
+
if (!content.includes("PI SESSION-START DELIVERY TRUNCATED")) throw new Error("truncation marker was lost");
|
|
927
|
+
if (content.includes("PI_LARGE_DIGEST_SUFFIX")) throw new Error("delivery exceeded its declared bound");
|
|
928
|
+
if (!content.includes("FIRSTMATE_OP: v1 session-start:")) throw new Error("operational provenance was lost");
|
|
929
|
+
JS
|
|
930
|
+
) || status=$?
|
|
931
|
+
expect_code 0 "$status" "Pi large session-start delivery"
|
|
932
|
+
[ -z "$out" ] || fail "Pi large session-start delivery printed output: $out"
|
|
933
|
+
pass "Pi retains a bounded digest prefix and loudly marks oversized preflight delivery"
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
test_run_resume_delegates_to_the_nudge() {
|
|
937
|
+
local root="$TMP_ROOT/run-resume" out status=0
|
|
938
|
+
make_run_primary "$root"
|
|
939
|
+
out=$(run_hook "$root" --source resume </dev/null) || status=$?
|
|
940
|
+
expect_code 0 "$status" "run wrapper resume"
|
|
941
|
+
[ "$out" = "$NUDGE_LINE" ] || fail "resume did not delegate to the exact nudge line, got: $out"
|
|
942
|
+
assert_absent "$root/state/.lock" "resume acquired the fleet lock instead of delegating"
|
|
943
|
+
pass "run wrapper: resume delegates to the nudge instead of re-running the digest"
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
test_run_reads_source_from_the_hook_payload() {
|
|
947
|
+
local root="$TMP_ROOT/run-payload" out status=0
|
|
948
|
+
make_run_primary "$root"
|
|
949
|
+
run_hook "$root" --source startup </dev/null >/dev/null
|
|
950
|
+
out=$(printf '{"session_id":"s1","hook_event_name":"SessionStart","source":"compact"}' |
|
|
951
|
+
run_hook "$root") || status=$?
|
|
952
|
+
expect_code 0 "$status" "run wrapper payload compact"
|
|
953
|
+
assert_contains "$out" "$REEMIT_BANNER$root" "a compact hook payload was not routed to a re-emit"
|
|
954
|
+
|
|
955
|
+
# A fresh root, because the compact case above legitimately took the lock and
|
|
956
|
+
# an owned lock is exactly when the nudge is supposed to stay silent.
|
|
957
|
+
root="$TMP_ROOT/run-payload-resume"
|
|
958
|
+
make_run_primary "$root"
|
|
959
|
+
status=0
|
|
960
|
+
out=$(printf '{"source":"resume","cwd":"/nowhere"}' | run_hook "$root") || status=$?
|
|
961
|
+
expect_code 0 "$status" "run wrapper payload resume"
|
|
962
|
+
assert_contains "$out" "FIRSTMATE_OP" "a resume hook payload did not delegate to the nudge"
|
|
963
|
+
assert_not_contains "$out" "SESSION START" "a resume hook payload still ran the digest"
|
|
964
|
+
pass "run wrapper: the hook payload's source field drives routing with no explicit argument"
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
test_run_unknown_source_takes_the_helm() {
|
|
968
|
+
local root="$TMP_ROOT/run-unknown" out status=0
|
|
969
|
+
make_run_primary "$root"
|
|
970
|
+
out=$(run_hook "$root" --source somethingnew </dev/null) || status=$?
|
|
971
|
+
expect_code 0 "$status" "run wrapper unknown source"
|
|
972
|
+
assert_contains "$out" "$FULL_BANNER$root" "an unrecognized source did not fall through to the full digest"
|
|
973
|
+
|
|
974
|
+
status=0
|
|
975
|
+
out=$(printf '{"hook_event_name":"SessionStart"}' | run_hook "$root") || status=$?
|
|
976
|
+
expect_code 0 "$status" "run wrapper sourceless payload"
|
|
977
|
+
assert_contains "$out" "$FULL_BANNER$root" "a payload with no source did not fall through to the full digest"
|
|
978
|
+
pass "run wrapper: an unrecognized or absent source takes the helm rather than skipping it"
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
test_run_gate_and_scope_are_silent() {
|
|
982
|
+
local root="$TMP_ROOT/run-gate" base="$TMP_ROOT/run-linked-base" linked="$TMP_ROOT/run-linked"
|
|
983
|
+
local out status=0
|
|
984
|
+
make_run_primary "$root"
|
|
985
|
+
expect_silent_zero "gate env run" env NO_MISTAKES_GATE=1 FM_GATE_REFUSE_BYPASS=0 \
|
|
986
|
+
FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" "$RUN" --source startup
|
|
987
|
+
assert_absent "$root/state/.lock" "a gate agent's session open still took the fleet lock"
|
|
988
|
+
out=$(env NO_MISTAKES_GATE=1 FM_GATE_REFUSE_BYPASS=0 \
|
|
989
|
+
FM_ROOT_OVERRIDE="$root" FM_HOME="$root" PATH="$RUN_PATH" \
|
|
990
|
+
"$RUN" --source startup --pi-prerequisite 2>&1) || status=$?
|
|
991
|
+
expect_code 3 "$status" "gate env Pi prerequisite stand-down"
|
|
992
|
+
[ -z "$out" ] || fail "gate env Pi prerequisite stand-down must be silent, got: $out"
|
|
993
|
+
|
|
994
|
+
fm_git_worktree "$base" "$linked" fm/run-linked
|
|
995
|
+
mkdir -p "$linked/bin" "$linked/state"
|
|
996
|
+
: > "$linked/AGENTS.md"
|
|
997
|
+
expect_silent_zero "linked worktree run" run_hook "$linked" --source startup
|
|
998
|
+
status=0
|
|
999
|
+
out=$(run_hook "$linked" --source startup --pi-prerequisite 2>&1) || status=$?
|
|
1000
|
+
expect_code 3 "$status" "linked worktree Pi prerequisite stand-down"
|
|
1001
|
+
[ -z "$out" ] || fail "linked worktree Pi prerequisite stand-down must be silent, got: $out"
|
|
1002
|
+
assert_absent "$linked/state/.lock" "an unmarked task worktree still took the fleet lock"
|
|
1003
|
+
pass "run wrapper: ordinary ineligible opens stay silent-zero and Pi preflight gets an explicit silent stand-down"
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
test_run_reports_a_failed_session_start_as_digest_text() {
|
|
1007
|
+
local root="$TMP_ROOT/run-unwritable" out status=0
|
|
1008
|
+
make_run_primary "$root"
|
|
1009
|
+
chmod 0500 "$root/state"
|
|
1010
|
+
out=$(run_hook "$root" --source startup </dev/null) || status=$?
|
|
1011
|
+
chmod 0700 "$root/state"
|
|
1012
|
+
expect_code 0 "$status" "run wrapper with an unwritable state directory"
|
|
1013
|
+
assert_contains "$out" "READ-ONLY SESSION" "a failed lock did not reach the agent as digest text"
|
|
1014
|
+
pass "run wrapper: a session start that cannot take the lock still opens the session and says so"
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
test_genuine_primary_nudges
|
|
1018
|
+
test_gate_env_is_silent
|
|
1019
|
+
test_gate_common_dir_is_silent
|
|
1020
|
+
test_unmarked_linked_worktree_is_silent
|
|
1021
|
+
test_linked_secondmate_primary_nudges
|
|
1022
|
+
test_missing_state_is_silent
|
|
1023
|
+
test_owned_lock_is_silent
|
|
1024
|
+
test_opencode_plugin_delivers_exact_nudge_once
|
|
1025
|
+
test_run_startup_runs_the_full_digest
|
|
1026
|
+
test_run_clear_and_compact_reemit
|
|
1027
|
+
test_run_rebuild_forwards_source_to_drifted_instruction_refresh
|
|
1028
|
+
test_run_compact_without_completion_refreshes_before_finishing_startup
|
|
1029
|
+
test_run_clear_without_completion_finishes_startup
|
|
1030
|
+
test_run_clear_rejects_previous_owner_completion
|
|
1031
|
+
test_run_resume_delegates_to_the_nudge
|
|
1032
|
+
test_run_reads_source_from_the_hook_payload
|
|
1033
|
+
test_run_unknown_source_takes_the_helm
|
|
1034
|
+
test_run_gate_and_scope_are_silent
|
|
1035
|
+
test_run_reports_a_failed_session_start_as_digest_text
|
|
1036
|
+
test_pi_startup_classifies_cli_continuations
|
|
1037
|
+
test_pi_sessionstart_generation_prerequisite
|
|
1038
|
+
test_pi_reload_releases_sessionstart_exit_listener
|
|
1039
|
+
test_pi_large_sessionstart_digest_is_delivered_loudly
|