@zerwiz/ymir 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/README.md +74 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +360 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +11 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +121 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/migrations/0005-roots-out-of-tree.sh +158 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +159 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +736 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +539 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +763 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +415 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +133 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +101 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +180 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +503 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +90 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +57 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +35 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/install.md +69 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +498 -0
- package/CHANGELOG.md +1913 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +114 -60
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +97 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +108 -0
- package/RULES/07-config.md +101 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/09-electron.md +58 -0
- package/RULES/README.md +22 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +133 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +256 -0
- package/bin/apodex-smoke-test.sh +105 -0
- package/bin/bifrost-bridge.sh +131 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +112 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +51 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-assemble.sh +154 -0
- package/bin/changelog-guard.sh +197 -0
- package/bin/crash-sense.sh +173 -0
- package/bin/design-check.sh +80 -0
- package/bin/design-icon.sh +153 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +225 -0
- package/bin/eindri-acclaim.sh +47 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seat.sh +83 -0
- package/bin/eindri-seen.sh +54 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +98 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +158 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/feature-inventory.sh +73 -0
- package/bin/fleet-apply.sh +81 -0
- package/bin/github-deploy.sh +94 -0
- package/bin/gjallarhorn-expose.sh +136 -0
- package/bin/gjallarhorn-notify.sh +71 -0
- package/bin/gjallarhorn-purge.sh +67 -0
- package/bin/gjallarhorn-tunnel.sh +85 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +130 -0
- package/bin/hall-snapshot.sh +111 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +452 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hlidskjalf-agents.sh +172 -0
- package/bin/hlidskjalf-usage.sh +196 -0
- package/bin/hoard-guard.sh +135 -0
- package/bin/hoard-lib.sh +105 -0
- package/bin/hodd.sh +102 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +121 -0
- package/bin/local-model-lock.sh +70 -0
- package/bin/mimir-bridge.py +259 -0
- package/bin/mimir-bridge.sh +100 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir-reflect.sh +27 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +72 -0
- package/bin/mjollnir.sh +104 -0
- package/bin/model-hardware.sh +96 -0
- package/bin/model-resolve.sh +160 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-hall-snapshot.sh +28 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-nsr-compliance.sh +54 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/npm-publish.sh +136 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +161 -0
- package/bin/omarchy-plugins.sh +187 -0
- package/bin/omarchy-sense.sh +162 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-model-agnostic.sh +71 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +58 -0
- package/bin/project-git.sh +69 -0
- package/bin/public-guard.sh +71 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +104 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +94 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +89 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-spawn-pretool-check.sh +58 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +203 -0
- package/bin/telegram-bot.sh +82 -0
- package/bin/toolchain.sh +74 -0
- package/bin/ui-truth-check.sh +75 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +298 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +60 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +163 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +806 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-plan.sh +356 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +110 -0
- package/bin/ymir-setup-auth.sh +119 -0
- package/bin/ymir-validate.sh +193 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1060 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +54 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +269 -0
- package/scripts/lower.sh +34 -0
- package/scripts/raise.sh +42 -0
- package/scripts/start.sh +217 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1463 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fm-fleet-snapshot.sh - read-only structured fleet snapshot.
|
|
3
|
+
#
|
|
4
|
+
# Output contract: `--json` prints one object with schema
|
|
5
|
+
# `fm-fleet-snapshot.v1`.
|
|
6
|
+
# The command is read-only: it does not acquire the session lock, drain wakes,
|
|
7
|
+
# arm watchers, mutate backlog state, or write reports.
|
|
8
|
+
#
|
|
9
|
+
# Top-level fields:
|
|
10
|
+
# schema: stable schema id.
|
|
11
|
+
# generated: UTC observation time for this fresh command execution.
|
|
12
|
+
# fm_home: resolved operational home.
|
|
13
|
+
# roots: resolved root/config/data/state/projects directories.
|
|
14
|
+
# backlog: {path,present,records[]} where records are ordered as written in
|
|
15
|
+
# data/backlog.md and cover In flight, Queued, and Done.
|
|
16
|
+
# Canonical tasks-axi rows are structured; free-form non-empty lines in
|
|
17
|
+
# those sections are preserved as unstructured records.
|
|
18
|
+
# Structured rows preserve captain-hold metadata such as hold_kind,
|
|
19
|
+
# hold_reason, and hold_until when tasks-axi emits it. They also carry
|
|
20
|
+
# normalized current_role, requires_child_metadata, blocked_by_ids,
|
|
21
|
+
# unresolved_blocker_ids, captain_actionable, and deferred_marker fields.
|
|
22
|
+
# Repeated blocker tokens remain ordered; a blocker resolves only when its
|
|
23
|
+
# structured record is Done, and missing ids stay open.
|
|
24
|
+
# captain_actionable means "waiting on the captain now": queued, held for
|
|
25
|
+
# the captain, unblocked, and due (no hold_until, or hold_until at or
|
|
26
|
+
# before the observation date, matching tasks-axi's own date-gate rule).
|
|
27
|
+
# There is no separate decision type: any captain-held task is the same
|
|
28
|
+
# primitive, whatever kind its row carries.
|
|
29
|
+
# deferred_marker is a presentation hint only: the row's hold reason or
|
|
30
|
+
# body carries an explicit SUPERSEDED / NOT REQUIRED / DEFERRED marker.
|
|
31
|
+
# It never changes captain_actionable; renderers may use it to keep
|
|
32
|
+
# prose-deferred rows out of default views.
|
|
33
|
+
# tasks[]: one row per state/<id>.meta, sorted by id.
|
|
34
|
+
# current_state is parsed from bin/fm-crew-state.sh <id> and preserves
|
|
35
|
+
# state, source, detail, and raw line separately.
|
|
36
|
+
# paths.status_log.last_event is historical wake-event data only, never
|
|
37
|
+
# current state.
|
|
38
|
+
# hints.open_decisions is the keyed open-decision set returned by
|
|
39
|
+
# fm-classify-lib.sh's authoritative status_open_decisions fold and reconciled
|
|
40
|
+
# against current_state; hints.pending_decision and hints.blocked_event are
|
|
41
|
+
# booleans derived from that set.
|
|
42
|
+
# endpoint.exists is the cheap backend endpoint-presence read.
|
|
43
|
+
# endpoint.agent_alive is populated for secondmates only, where it is useful
|
|
44
|
+
# return-channel supervision data; other tasks use "not_checked".
|
|
45
|
+
# scout_reports[]: present data/<id>/report.md pointers.
|
|
46
|
+
# main_inventory: {valid,reason,orphan_in_flight[],unstructured_current_count} -
|
|
47
|
+
# main-home current-inventory checks shared with secondmate_home_summary_json
|
|
48
|
+
# (orphan structured in-flight ids with no state/<id>.meta, and unstructured
|
|
49
|
+
# current backlog rows). Does not invent live tasks; meta remains truth for
|
|
50
|
+
# workers. Bearings maps failures into omitted[] disclosure (and a Charted
|
|
51
|
+
# Next gate line) rather than silent empty Underway.
|
|
52
|
+
# secondmate_current: {records[],total,shown,truncated} - bounded current summaries
|
|
53
|
+
# for registered secondmates, selected from validated structured state inside
|
|
54
|
+
# each home with explicit provenance, freshness, endpoint evidence, and unknown
|
|
55
|
+
# failure reasons. Parent status and bounded terminal evidence are historical,
|
|
56
|
+
# untrusted supplements only and never override readable structured-home facts.
|
|
57
|
+
# Each structured-home record carries active_children, decisions_open, holds,
|
|
58
|
+
# queued, landed, endpoints, counts, and omitted. Every successfully sampled
|
|
59
|
+
# home also carries reconcile_inventory independently of projection trust.
|
|
60
|
+
# Actionable captain holds
|
|
61
|
+
# appear in decisions_open; blocked captain holds remain queued with metadata.
|
|
62
|
+
# secondmate_landed: {records[],truncated[],unreadable[],partial[]} - the
|
|
63
|
+
# compatibility landed-work roll-up derived from secondmate_current. Readable
|
|
64
|
+
# structured homes are partial, not unreadable, when an unavailable child state
|
|
65
|
+
# or a backlog-vs-metadata inventory mismatch makes their summary incomplete;
|
|
66
|
+
# they retain independently trustworthy structured surfaces. An inventory
|
|
67
|
+
# mismatch also keeps the home's own current classification, which only an
|
|
68
|
+
# unavailable child state or an untrustworthy backlog collapses to unknown.
|
|
69
|
+
# secondmate_guidance: return-channel action note for renderers and bearings.
|
|
70
|
+
#
|
|
71
|
+
# Compatibility: JSON is the primary machine-readable surface.
|
|
72
|
+
# Human views must render this output instead of parsing state files again.
|
|
73
|
+
set -u
|
|
74
|
+
|
|
75
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
76
|
+
FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
77
|
+
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
|
|
78
|
+
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
|
|
79
|
+
DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
|
|
80
|
+
CONFIG="${FM_CONFIG_OVERRIDE:-$FM_HOME/config}"
|
|
81
|
+
PROJECTS="${FM_PROJECTS_OVERRIDE:-$FM_HOME/projects}"
|
|
82
|
+
BACKLOG="$DATA/backlog.md"
|
|
83
|
+
SNAPSHOT_NOW=${FM_SNAPSHOT_NOW:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}
|
|
84
|
+
if [ -n "${FM_SNAPSHOT_NOW_EPOCH:-}" ]; then
|
|
85
|
+
SNAPSHOT_EPOCH=$FM_SNAPSHOT_NOW_EPOCH
|
|
86
|
+
else
|
|
87
|
+
SNAPSHOT_EPOCH=$(date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$SNAPSHOT_NOW" +%s 2>/dev/null \
|
|
88
|
+
|| date -u -d "$SNAPSHOT_NOW" +%s 2>/dev/null \
|
|
89
|
+
|| date +%s)
|
|
90
|
+
fi
|
|
91
|
+
case "$SNAPSHOT_EPOCH" in ''|*[!0-9]*) SNAPSHOT_EPOCH=$(date +%s) ;; esac
|
|
92
|
+
# The observation date gates captain-hold deferral: a `hold-until` date still in
|
|
93
|
+
# the future keeps a captain hold out of captain_actionable until it is due
|
|
94
|
+
# (tasks-axi's own contract: the hold is inactive on and after that date).
|
|
95
|
+
SNAPSHOT_TODAY=${SNAPSHOT_NOW%%T*}
|
|
96
|
+
case "$SNAPSHOT_TODAY" in
|
|
97
|
+
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) : ;;
|
|
98
|
+
*) SNAPSHOT_TODAY=$(date -u +%Y-%m-%d) ;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
# Cross-home bounds are explicit so one broken or unexpectedly large home cannot
|
|
102
|
+
# hang or explode the parent snapshot.
|
|
103
|
+
FM_SNAPSHOT_SECONDMATES=${FM_SNAPSHOT_SECONDMATES:-20}
|
|
104
|
+
FM_SNAPSHOT_SECONDMATE_TIMEOUT=${FM_SNAPSHOT_SECONDMATE_TIMEOUT:-8}
|
|
105
|
+
FM_SNAPSHOT_CREW_STATE_TIMEOUT=${FM_SNAPSHOT_CREW_STATE_TIMEOUT:-10}
|
|
106
|
+
FM_SNAPSHOT_SECONDMATE_MAX_BYTES=${FM_SNAPSHOT_SECONDMATE_MAX_BYTES:-262144}
|
|
107
|
+
FM_SNAPSHOT_SECONDMATE_CHILDREN=${FM_SNAPSHOT_SECONDMATE_CHILDREN:-20}
|
|
108
|
+
FM_SNAPSHOT_SECONDMATE_QUEUED=${FM_SNAPSHOT_SECONDMATE_QUEUED:-20}
|
|
109
|
+
FM_SNAPSHOT_SECONDMATE_DECISIONS=${FM_SNAPSHOT_SECONDMATE_DECISIONS:-20}
|
|
110
|
+
FM_SNAPSHOT_TERMINAL_LINES=${FM_SNAPSHOT_TERMINAL_LINES:-8}
|
|
111
|
+
FM_SNAPSHOT_TERMINAL_BYTES=${FM_SNAPSHOT_TERMINAL_BYTES:-4096}
|
|
112
|
+
FM_SNAPSHOT_TERMINAL_TIMEOUT=${FM_SNAPSHOT_TERMINAL_TIMEOUT:-2}
|
|
113
|
+
FM_SNAPSHOT_PARENT_ACTIVITY_LINES=${FM_SNAPSHOT_PARENT_ACTIVITY_LINES:-256}
|
|
114
|
+
FM_SNAPSHOT_PARENT_ACTIVITY_BYTES=${FM_SNAPSHOT_PARENT_ACTIVITY_BYTES:-65536}
|
|
115
|
+
FM_SNAPSHOT_PARENT_ACTIVITIES=${FM_SNAPSHOT_PARENT_ACTIVITIES:-20}
|
|
116
|
+
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT=${FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT:-2}
|
|
117
|
+
FM_SNAPSHOT_REGISTRY_LINES=${FM_SNAPSHOT_REGISTRY_LINES:-256}
|
|
118
|
+
FM_SNAPSHOT_REGISTRY_BYTES=${FM_SNAPSHOT_REGISTRY_BYTES:-65536}
|
|
119
|
+
FM_SNAPSHOT_REGISTRY_RECORDS=${FM_SNAPSHOT_REGISTRY_RECORDS:-40}
|
|
120
|
+
FM_SNAPSHOT_REGISTRY_TIMEOUT=${FM_SNAPSHOT_REGISTRY_TIMEOUT:-2}
|
|
121
|
+
validate_positive_bound() { # <name> <value>
|
|
122
|
+
case "$2" in
|
|
123
|
+
''|*[!0-9]*|0)
|
|
124
|
+
printf 'fm-fleet-snapshot: %s must be a positive integer\n' "$1" >&2
|
|
125
|
+
exit 2
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
}
|
|
129
|
+
case "$FM_SNAPSHOT_SECONDMATES" in
|
|
130
|
+
''|*[!0-9]*)
|
|
131
|
+
echo "fm-fleet-snapshot: FM_SNAPSHOT_SECONDMATES must be a non-negative integer" >&2
|
|
132
|
+
exit 2
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
135
|
+
validate_positive_bound FM_SNAPSHOT_SECONDMATE_TIMEOUT "$FM_SNAPSHOT_SECONDMATE_TIMEOUT"
|
|
136
|
+
validate_positive_bound FM_SNAPSHOT_CREW_STATE_TIMEOUT "$FM_SNAPSHOT_CREW_STATE_TIMEOUT"
|
|
137
|
+
validate_positive_bound FM_SNAPSHOT_SECONDMATE_MAX_BYTES "$FM_SNAPSHOT_SECONDMATE_MAX_BYTES"
|
|
138
|
+
validate_positive_bound FM_SNAPSHOT_SECONDMATE_CHILDREN "$FM_SNAPSHOT_SECONDMATE_CHILDREN"
|
|
139
|
+
validate_positive_bound FM_SNAPSHOT_SECONDMATE_QUEUED "$FM_SNAPSHOT_SECONDMATE_QUEUED"
|
|
140
|
+
validate_positive_bound FM_SNAPSHOT_SECONDMATE_DECISIONS "$FM_SNAPSHOT_SECONDMATE_DECISIONS"
|
|
141
|
+
validate_positive_bound FM_SNAPSHOT_TERMINAL_LINES "$FM_SNAPSHOT_TERMINAL_LINES"
|
|
142
|
+
validate_positive_bound FM_SNAPSHOT_TERMINAL_BYTES "$FM_SNAPSHOT_TERMINAL_BYTES"
|
|
143
|
+
validate_positive_bound FM_SNAPSHOT_TERMINAL_TIMEOUT "$FM_SNAPSHOT_TERMINAL_TIMEOUT"
|
|
144
|
+
validate_positive_bound FM_SNAPSHOT_PARENT_ACTIVITY_LINES "$FM_SNAPSHOT_PARENT_ACTIVITY_LINES"
|
|
145
|
+
validate_positive_bound FM_SNAPSHOT_PARENT_ACTIVITY_BYTES "$FM_SNAPSHOT_PARENT_ACTIVITY_BYTES"
|
|
146
|
+
validate_positive_bound FM_SNAPSHOT_PARENT_ACTIVITIES "$FM_SNAPSHOT_PARENT_ACTIVITIES"
|
|
147
|
+
validate_positive_bound FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT "$FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT"
|
|
148
|
+
validate_positive_bound FM_SNAPSHOT_REGISTRY_LINES "$FM_SNAPSHOT_REGISTRY_LINES"
|
|
149
|
+
validate_positive_bound FM_SNAPSHOT_REGISTRY_BYTES "$FM_SNAPSHOT_REGISTRY_BYTES"
|
|
150
|
+
validate_positive_bound FM_SNAPSHOT_REGISTRY_RECORDS "$FM_SNAPSHOT_REGISTRY_RECORDS"
|
|
151
|
+
validate_positive_bound FM_SNAPSHOT_REGISTRY_TIMEOUT "$FM_SNAPSHOT_REGISTRY_TIMEOUT"
|
|
152
|
+
|
|
153
|
+
# shellcheck source=bin/fm-backend.sh
|
|
154
|
+
# shellcheck disable=SC1091
|
|
155
|
+
. "$SCRIPT_DIR/fm-backend.sh"
|
|
156
|
+
# shellcheck source=bin/fm-classify-lib.sh
|
|
157
|
+
# shellcheck disable=SC1091
|
|
158
|
+
. "$SCRIPT_DIR/fm-classify-lib.sh"
|
|
159
|
+
# shellcheck source=bin/fm-ff-lib.sh
|
|
160
|
+
# shellcheck disable=SC1091
|
|
161
|
+
. "$SCRIPT_DIR/fm-ff-lib.sh" # validate_secondmate_home: shared seeded-home boundary checks
|
|
162
|
+
# shellcheck source=bin/fm-timeout-lib.sh
|
|
163
|
+
# shellcheck disable=SC1091
|
|
164
|
+
. "$SCRIPT_DIR/fm-timeout-lib.sh" # fm_run_timed: the shared hard bound
|
|
165
|
+
|
|
166
|
+
usage() {
|
|
167
|
+
cat <<'EOF'
|
|
168
|
+
usage: fm-fleet-snapshot.sh --json
|
|
169
|
+
fm-fleet-snapshot.sh --secondmate-home-summary
|
|
170
|
+
|
|
171
|
+
Print a read-only structured snapshot of the firstmate fleet.
|
|
172
|
+
JSON is the stable machine-readable output contract.
|
|
173
|
+
|
|
174
|
+
--secondmate-home-summary emits the bounded structured summary used after a
|
|
175
|
+
validated registered-home handoff. It is local-only, skips nested secondmate
|
|
176
|
+
aggregation, includes generated_epoch for freshness arithmetic, and marks
|
|
177
|
+
inventory contradictions or unavailable child state invalid.
|
|
178
|
+
Its invalidity object names the normalized failure kind and affected ids.
|
|
179
|
+
Actionable tasks-axi captain holds appear as decisions_open and stay visible in
|
|
180
|
+
queued with hold_reason, hold_kind, hold_until, deferred_marker, and plural
|
|
181
|
+
blocker fields for downstream projections. A captain hold is actionable only
|
|
182
|
+
when every blocker is Done and any hold-until date has arrived.
|
|
183
|
+
Cross-home reads use FM_SNAPSHOT_SECONDMATES (default 20, 0 lifts the count
|
|
184
|
+
bound), FM_SNAPSHOT_SECONDMATE_TIMEOUT, and FM_SNAPSHOT_SECONDMATE_MAX_BYTES.
|
|
185
|
+
Each per-task current-state read is bounded by FM_SNAPSHOT_CREW_STATE_TIMEOUT
|
|
186
|
+
(default 10 seconds), so one unreachable remote secondmate host cannot extend
|
|
187
|
+
the snapshot without limit; a read that hits the bound reports state unknown.
|
|
188
|
+
Terminal contradiction evidence uses
|
|
189
|
+
FM_SNAPSHOT_TERMINAL_LINES, FM_SNAPSHOT_TERMINAL_BYTES, and
|
|
190
|
+
FM_SNAPSHOT_TERMINAL_TIMEOUT and never becomes canonical current state.
|
|
191
|
+
Parent activity evidence uses FM_SNAPSHOT_PARENT_ACTIVITY_LINES,
|
|
192
|
+
FM_SNAPSHOT_PARENT_ACTIVITY_BYTES, FM_SNAPSHOT_PARENT_ACTIVITIES, and
|
|
193
|
+
FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT, with truncation disclosed in the result.
|
|
194
|
+
The registered secondmate table uses FM_SNAPSHOT_REGISTRY_LINES,
|
|
195
|
+
FM_SNAPSHOT_REGISTRY_BYTES, FM_SNAPSHOT_REGISTRY_RECORDS, and
|
|
196
|
+
FM_SNAPSHOT_REGISTRY_TIMEOUT, with unavailability and truncation disclosed.
|
|
197
|
+
EOF
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
OUTPUT_MODE=json
|
|
201
|
+
case "${1:---json}" in
|
|
202
|
+
--json) ;;
|
|
203
|
+
--secondmate-home-summary) OUTPUT_MODE=secondmate-home-summary ;;
|
|
204
|
+
-h|--help) usage; exit 0 ;;
|
|
205
|
+
*) usage >&2; exit 2 ;;
|
|
206
|
+
esac
|
|
207
|
+
|
|
208
|
+
command -v jq >/dev/null 2>&1 || { echo "fm-fleet-snapshot: jq not found" >&2; exit 1; }
|
|
209
|
+
|
|
210
|
+
bool_json() {
|
|
211
|
+
if [ "$1" = 1 ]; then printf 'true'; else printf 'false'; fi
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
path_present_json() { # <path>
|
|
215
|
+
local present=0
|
|
216
|
+
[ -e "$1" ] && present=1
|
|
217
|
+
jq -n --arg path "$1" --argjson present "$(bool_json "$present")" \
|
|
218
|
+
'{path:$path,present:$present}'
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
meta_value() { # <meta-file> <key>
|
|
222
|
+
fm_meta_get "$1" "$2"
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
last_nonempty_line() { # <file>
|
|
226
|
+
[ -f "$1" ] || return 1
|
|
227
|
+
grep -v '^[[:space:]]*$' "$1" 2>/dev/null | tail -1
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# A crew-state read is bounded like every other cross-home read here. For a
|
|
231
|
+
# remote secondmate fm-crew-state.sh reaches its host over ssh, and ssh's own
|
|
232
|
+
# dead-peer detection deliberately never kills a slow-but-alive remote command,
|
|
233
|
+
# so without this bound one unreachable or slow host extends the whole snapshot
|
|
234
|
+
# without limit - and this snapshot is also the producer behind the repeatedly
|
|
235
|
+
# published home ledger. A read that hits the bound is indistinguishable from
|
|
236
|
+
# the already-handled unreadable case: empty output folds to state unknown.
|
|
237
|
+
crew_state_json() { # <id>
|
|
238
|
+
local id=$1 raw rest state source detail sep
|
|
239
|
+
raw=$(
|
|
240
|
+
fm_run_timed "$FM_SNAPSHOT_CREW_STATE_TIMEOUT" \
|
|
241
|
+
env FM_ROOT_OVERRIDE="$FM_ROOT" \
|
|
242
|
+
FM_HOME="$FM_HOME" \
|
|
243
|
+
FM_STATE_OVERRIDE="$STATE" \
|
|
244
|
+
FM_DATA_OVERRIDE="$DATA" \
|
|
245
|
+
FM_PROJECTS_OVERRIDE="$PROJECTS" \
|
|
246
|
+
FM_CONFIG_OVERRIDE="$CONFIG" \
|
|
247
|
+
"$SCRIPT_DIR/fm-crew-state.sh" "$id" 2>/dev/null || true
|
|
248
|
+
)
|
|
249
|
+
raw=$(printf '%s\n' "$raw" | head -1)
|
|
250
|
+
sep=' · '
|
|
251
|
+
state=unknown
|
|
252
|
+
source=none
|
|
253
|
+
detail=
|
|
254
|
+
case "$raw" in
|
|
255
|
+
state:\ *"$sep"source:\ *)
|
|
256
|
+
rest=${raw#state: }
|
|
257
|
+
state=${rest%%"$sep"source: *}
|
|
258
|
+
rest=${rest#*"$sep"source: }
|
|
259
|
+
case "$rest" in
|
|
260
|
+
*"$sep"*) source=${rest%%"$sep"*}; detail=${rest#*"$sep"} ;;
|
|
261
|
+
*) source=$rest ;;
|
|
262
|
+
esac
|
|
263
|
+
;;
|
|
264
|
+
esac
|
|
265
|
+
jq -n --arg raw "$raw" --arg state "$state" --arg source "$source" --arg detail "$detail" \
|
|
266
|
+
'{state:$state,source:$source,detail:$detail,raw:$raw}'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
status_event_json() { # <status-log>
|
|
270
|
+
local log=$1 present=0 raw='' verb='' note=''
|
|
271
|
+
if [ -f "$log" ]; then
|
|
272
|
+
present=1
|
|
273
|
+
raw=$(last_nonempty_line "$log" || true)
|
|
274
|
+
verb=$(status_line_verb "$raw")
|
|
275
|
+
note=$(status_line_note "$raw")
|
|
276
|
+
fi
|
|
277
|
+
jq -n \
|
|
278
|
+
--arg path "$log" \
|
|
279
|
+
--arg raw "$raw" \
|
|
280
|
+
--arg verb "$verb" \
|
|
281
|
+
--arg note "$note" \
|
|
282
|
+
--argjson present "$(bool_json "$present")" \
|
|
283
|
+
'{path:$path,present:$present,kind:"event_history",last_event:{state:$verb,note:$note,raw:$raw}}'
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
first_pr_url_in_file() { # <file>
|
|
287
|
+
[ -f "$1" ] || return 1
|
|
288
|
+
grep -Eo 'https?://[^[:space:])"]+/pull/[0-9]+' "$1" 2>/dev/null | head -1
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
backlog_json() { # [<backlog-path>] - defaults to this home's $BACKLOG
|
|
292
|
+
local backlog=${1:-$BACKLOG}
|
|
293
|
+
if [ ! -f "$backlog" ]; then
|
|
294
|
+
jq -n --arg path "$backlog" '{path:$path,present:false,records:[]}'
|
|
295
|
+
return 0
|
|
296
|
+
fi
|
|
297
|
+
|
|
298
|
+
# shellcheck disable=SC2094
|
|
299
|
+
jq -Rn --arg path "$backlog" --arg today "$SNAPSHOT_TODAY" '
|
|
300
|
+
def trim: gsub("^[[:space:]]+|[[:space:]]+$"; "");
|
|
301
|
+
def section_state:
|
|
302
|
+
if . == "In flight" then "in_flight"
|
|
303
|
+
elif . == "Queued" then "queued"
|
|
304
|
+
elif . == "Done" then "done"
|
|
305
|
+
else null end;
|
|
306
|
+
def cap($rest; $re):
|
|
307
|
+
(((($rest | capture($re)?) // {}) | .v) // null) as $v
|
|
308
|
+
| if $v == null then null else ($v | trim) end;
|
|
309
|
+
def metadata($rest; $key):
|
|
310
|
+
cap($rest; ".*(?:\\(|,[[:space:]]*)" + $key + ":[[:space:]]*(?<v>[^,)]*)");
|
|
311
|
+
def metadata_word($rest; $key):
|
|
312
|
+
cap($rest; ".*(?:\\(|,[[:space:]]*)" + $key + "[[:space:]]+(?<v>[^,)]*)");
|
|
313
|
+
def url_pattern: "https?://[^[:space:])\"<>]+";
|
|
314
|
+
def wrapped_url_pattern: "<?" + url_pattern + ">?";
|
|
315
|
+
def links($rest): [$rest | scan(url_pattern)];
|
|
316
|
+
def strip_trailing_metadata:
|
|
317
|
+
reduce range(0; 20) as $_ (.;
|
|
318
|
+
sub("[[:space:]]*\\([[:space:]]*(?:(?:repo|kind|priority|hold|hold-kind|hold-until):[[:space:]]*[^)]*|(?:since|merged|reported|done)[[:space:]]+[^)]*)[[:space:]]*\\)[[:space:]]*$"; ""));
|
|
319
|
+
def strip_title_artifacts:
|
|
320
|
+
sub("[[:space:]]+-[[:space:]]+data/[^[:space:])]+/report\\.md$"; "")
|
|
321
|
+
| sub("[[:space:]]+data/[^[:space:])]+/report\\.md$"; "")
|
|
322
|
+
| sub("[[:space:]]+-[[:space:]]+local main$"; "")
|
|
323
|
+
| sub("[[:space:]]+local main$"; "")
|
|
324
|
+
| sub("[[:space:]]+-[[:space:]]*$"; "");
|
|
325
|
+
def clean_title:
|
|
326
|
+
strip_trailing_metadata
|
|
327
|
+
| strip_title_artifacts
|
|
328
|
+
| gsub("[[:space:]]+"; " ")
|
|
329
|
+
| trim;
|
|
330
|
+
def title_of($rest):
|
|
331
|
+
$rest
|
|
332
|
+
| gsub(wrapped_url_pattern; "")
|
|
333
|
+
| sub("[[:space:]]*blocked-by:[[:space:]]+[^[:space:])]+[[:space:]]+-[[:space:]]+.*$"; "")
|
|
334
|
+
| gsub("[[:space:]]*blocked-by:[[:space:]]+[^[:space:]]+"; "")
|
|
335
|
+
| clean_title;
|
|
336
|
+
def blocked_by_ids($rest):
|
|
337
|
+
[ $rest | scan("blocked-by:[[:space:]]+(?<id>[^[:space:])]+)") | .[0] ]
|
|
338
|
+
| reduce .[] as $id ([]; if index($id) == null then . + [$id] else . end);
|
|
339
|
+
def blocked_reason($rest):
|
|
340
|
+
cap($rest; ".*blocked-by:[[:space:]]*[^[:space:])]+[[:space:]]+-[[:space:]]*(?<v>.*)$") as $reason
|
|
341
|
+
| if $reason == null then null
|
|
342
|
+
else ($reason | clean_title | if . == "" then null else . end)
|
|
343
|
+
end;
|
|
344
|
+
def local_note($rest):
|
|
345
|
+
cap(($rest | strip_trailing_metadata); ".*(?:^|[[:space:]]+-[[:space:]]+|[[:space:]])(?<v>local main)$");
|
|
346
|
+
def completion($rest):
|
|
347
|
+
(metadata_word($rest; "merged")) as $merged
|
|
348
|
+
| (metadata_word($rest; "reported")) as $reported
|
|
349
|
+
| (metadata_word($rest; "done")) as $done
|
|
350
|
+
| if $merged != null then {verb:"merged",date:$merged}
|
|
351
|
+
elif $reported != null then {verb:"reported",date:$reported}
|
|
352
|
+
elif $done != null then {verb:"done",date:$done}
|
|
353
|
+
else {verb:null,date:null} end;
|
|
354
|
+
def row_match($line):
|
|
355
|
+
(($line | capture("^[-*][[:space:]]+\\[(?<check>[ xX])\\][[:space:]]+(?<id>[^[:space:]]+)[[:space:]]+-[[:space:]]+(?<rest>.*)$")?) //
|
|
356
|
+
(($line | capture("^[-*][[:space:]]+\\*\\*(?<id>[^*]+)\\*\\*[[:space:]]+-[[:space:]]+(?<rest>.*)$")?)
|
|
357
|
+
| if . == null then null else . + {check:" "} end));
|
|
358
|
+
def structured_row($line):
|
|
359
|
+
($line | test("^[-*][[:space:]]+\\[[ xX]\\][[:space:]]+[^[:space:]]+[[:space:]]+-[[:space:]]+"))
|
|
360
|
+
or ($line | test("^[-*][[:space:]]+\\*\\*[^*]+\\*\\*[[:space:]]+-[[:space:]]+"));
|
|
361
|
+
def parse_row($line; $section; $order):
|
|
362
|
+
row_match($line) as $m
|
|
363
|
+
| if $m == null then
|
|
364
|
+
{order:$order,state:$section,structured:false,id:null,raw:$line,body_lines:[],body_excerpt:null}
|
|
365
|
+
else
|
|
366
|
+
($m.rest) as $rest
|
|
367
|
+
| {order:$order,
|
|
368
|
+
state:$section,
|
|
369
|
+
structured:true,
|
|
370
|
+
id:($m.id | trim),
|
|
371
|
+
checked:($m.check | test("[xX]")),
|
|
372
|
+
title:title_of($rest),
|
|
373
|
+
repo:metadata($rest; "repo"),
|
|
374
|
+
kind:metadata($rest; "kind"),
|
|
375
|
+
priority:metadata($rest; "priority"),
|
|
376
|
+
hold_reason:metadata($rest; "hold"),
|
|
377
|
+
hold_kind:metadata($rest; "hold-kind"),
|
|
378
|
+
hold_until:metadata($rest; "hold-until"),
|
|
379
|
+
blocked_by:cap($rest; ".*blocked-by:[[:space:]]*(?<v>[^[:space:])]+).*"),
|
|
380
|
+
blocked_by_ids:blocked_by_ids($rest),
|
|
381
|
+
blocked_reason:blocked_reason($rest),
|
|
382
|
+
since:metadata_word($rest; "since"),
|
|
383
|
+
merged:metadata_word($rest; "merged"),
|
|
384
|
+
reported:metadata_word($rest; "reported"),
|
|
385
|
+
done:metadata_word($rest; "done"),
|
|
386
|
+
completion:completion($rest),
|
|
387
|
+
links:links($rest),
|
|
388
|
+
pr_url:((links($rest) | map(select(test("/pull/[0-9]+"))) | .[0]) // null),
|
|
389
|
+
report_path:cap($rest; ".*(?<v>data/[^[:space:])]+/report\\.md).*"),
|
|
390
|
+
local_note:local_note($rest),
|
|
391
|
+
raw:$line,
|
|
392
|
+
body_lines:[],
|
|
393
|
+
body_excerpt:null}
|
|
394
|
+
end;
|
|
395
|
+
reduce inputs as $line
|
|
396
|
+
({path:$path,present:true,records:[],section:null,order:0};
|
|
397
|
+
if ($line | test("^##[[:space:]]+")) then
|
|
398
|
+
.section = (($line | sub("^##[[:space:]]+";"") | trim) | section_state)
|
|
399
|
+
elif .section == null or ($line | trim) == "" then
|
|
400
|
+
.
|
|
401
|
+
elif structured_row($line) then
|
|
402
|
+
.order += 1
|
|
403
|
+
| .records += [parse_row($line; .section; .order)]
|
|
404
|
+
elif ((.records | length) > 0 and (.records[-1].structured == true) and ($line | test("^[[:space:]]+"))) then
|
|
405
|
+
($line | trim) as $body
|
|
406
|
+
| if $body == "" then .
|
|
407
|
+
else .records[-1].body_lines += [$body] end
|
|
408
|
+
else
|
|
409
|
+
.order += 1
|
|
410
|
+
| .records += [{order:.order,state:.section,structured:false,id:null,raw:$line,body_lines:[],body_excerpt:null}]
|
|
411
|
+
end)
|
|
412
|
+
| .records |= map(
|
|
413
|
+
if (.body_lines | length) > 0 then
|
|
414
|
+
.body_excerpt = ((.body_lines | join(" "))[:240])
|
|
415
|
+
else . end)
|
|
416
|
+
| .records as $records
|
|
417
|
+
| (reduce ($records[] | select(.structured)) as $record ({};
|
|
418
|
+
.[$record.id] = ((.[$record.id] // true) and ($record.state == "done")))) as $resolved_ids
|
|
419
|
+
| .records |= map(
|
|
420
|
+
if .structured then
|
|
421
|
+
. as $record
|
|
422
|
+
| .unresolved_blocker_ids = [
|
|
423
|
+
$record.blocked_by_ids[] as $blocker
|
|
424
|
+
| select($resolved_ids[$blocker] != true)
|
|
425
|
+
| $blocker
|
|
426
|
+
]
|
|
427
|
+
| .current_role =
|
|
428
|
+
(if .state == "in_flight" and .hold_reason != null and .hold_kind != null then "held"
|
|
429
|
+
elif .state == "in_flight" and .kind == "program" then "program"
|
|
430
|
+
elif .state == "in_flight" then "worker"
|
|
431
|
+
elif .state == "queued" then "queued"
|
|
432
|
+
else "done" end)
|
|
433
|
+
| .requires_child_metadata = (.current_role == "worker")
|
|
434
|
+
| .captain_actionable =
|
|
435
|
+
(.state == "queued" and .hold_kind == "captain"
|
|
436
|
+
and .hold_reason != null and (.unresolved_blocker_ids | length) == 0
|
|
437
|
+
and (.hold_until == null or .hold_until <= $today))
|
|
438
|
+
| .deferred_marker =
|
|
439
|
+
((((.hold_reason // "") + " " + (.body_excerpt // ""))
|
|
440
|
+
| test("SUPERSEDED|NOT REQUIRED|NOT-REQUIRED|DEFERRED"; "i")))
|
|
441
|
+
else . end)
|
|
442
|
+
| del(.section,.order)
|
|
443
|
+
' < "$backlog"
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
task_json_lines() {
|
|
447
|
+
local meta id kind harness mode yolo project worktree home projects spawn_gen backend target status_log report_path
|
|
448
|
+
local remote_host remote_root remote_state remote_rc remote_home_present
|
|
449
|
+
local pr pr_source event_json current_json endpoint_exists agent_alive meta_json status_json report_json worktree_json home_json
|
|
450
|
+
local last_event_raw current_state current_source pending_decision blocked_event report_present=0 pr_from_status
|
|
451
|
+
local open_decisions_tsv open_decisions_json
|
|
452
|
+
|
|
453
|
+
for meta in "$STATE"/*.meta; do
|
|
454
|
+
[ -e "$meta" ] || continue
|
|
455
|
+
id=$(basename "$meta" .meta)
|
|
456
|
+
kind=$(meta_value "$meta" kind)
|
|
457
|
+
[ -n "$kind" ] || kind=ship
|
|
458
|
+
harness=$(meta_value "$meta" harness)
|
|
459
|
+
mode=$(meta_value "$meta" mode)
|
|
460
|
+
yolo=$(meta_value "$meta" yolo)
|
|
461
|
+
project=$(meta_value "$meta" project)
|
|
462
|
+
worktree=$(meta_value "$meta" worktree)
|
|
463
|
+
home=$(meta_value "$meta" home)
|
|
464
|
+
projects=$(meta_value "$meta" projects)
|
|
465
|
+
spawn_gen=$(meta_value "$meta" spawn_gen)
|
|
466
|
+
remote_host=$(meta_value "$meta" remote_host)
|
|
467
|
+
remote_root=$(meta_value "$meta" remote_root)
|
|
468
|
+
remote_home_present=null
|
|
469
|
+
if [ -n "$remote_host" ]; then
|
|
470
|
+
backend=$(meta_value "$meta" remote_backend)
|
|
471
|
+
[ -n "$backend" ] || backend=unknown
|
|
472
|
+
target=$(meta_value "$meta" remote_target)
|
|
473
|
+
else
|
|
474
|
+
backend=$(fm_backend_of_meta "$meta")
|
|
475
|
+
target=$(fm_backend_target_of_meta "$meta")
|
|
476
|
+
fi
|
|
477
|
+
status_log="$STATE/$id.status"
|
|
478
|
+
report_path="$DATA/$id/report.md"
|
|
479
|
+
pr=$(meta_value "$meta" pr)
|
|
480
|
+
pr_source=meta
|
|
481
|
+
if [ -z "$pr" ]; then
|
|
482
|
+
pr_from_status=$(first_pr_url_in_file "$status_log" || true)
|
|
483
|
+
pr=$pr_from_status
|
|
484
|
+
pr_source=status_event
|
|
485
|
+
fi
|
|
486
|
+
if [ -z "$pr" ]; then
|
|
487
|
+
pr_source=absent
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
current_json=$(crew_state_json "$id")
|
|
491
|
+
event_json=$(status_event_json "$status_log")
|
|
492
|
+
last_event_raw=$(printf '%s' "$event_json" | jq -r '.last_event.raw // ""')
|
|
493
|
+
current_state=$(printf '%s' "$current_json" | jq -r '.state // ""')
|
|
494
|
+
current_source=$(printf '%s' "$current_json" | jq -r '.source // ""')
|
|
495
|
+
|
|
496
|
+
# Durable keyed open-decision set: fold the WHOLE status stream
|
|
497
|
+
# (fm-classify-lib.sh's status_open_decisions) so a later unrelated event can
|
|
498
|
+
# never mask a still-open captain decision. The set is derived purely from the
|
|
499
|
+
# keyed fold - never from report bodies or decision-like prose - and then
|
|
500
|
+
# reconciled against the crew LIFECYCLE, which only clears a stale decision the
|
|
501
|
+
# crew has provably moved past. Two lifecycle signals clear it, neither of which
|
|
502
|
+
# reads any report content:
|
|
503
|
+
# - a live activity read (run-step or busy pane) that is working/done, so a
|
|
504
|
+
# crew that resumed past a gate is not still reported as parked; and
|
|
505
|
+
# - a TERMINAL done/failed state on a single-owner task (scout or ship), whose
|
|
506
|
+
# deliverable is its report or PR, so a COMPLETED scout surfaces only as a
|
|
507
|
+
# report POINTER, never as a reopened pending decision.
|
|
508
|
+
# Secondmates are excluded from lifecycle clearing: they are persistent and
|
|
509
|
+
# multiplex many concerns onto one stream, so activity on one concern must
|
|
510
|
+
# never clear another concern's keyed decision. A parked/blocked state, or a
|
|
511
|
+
# non-authoritative status-log/none read on a still-live task, keeps the fold's
|
|
512
|
+
# open decision surfacing.
|
|
513
|
+
open_decisions_tsv=$(status_open_decisions "$status_log")
|
|
514
|
+
if [ "$kind" != secondmate ] && \
|
|
515
|
+
{ { { [ "$current_source" = run-step ] || [ "$current_source" = pane ]; } \
|
|
516
|
+
&& [ "$current_state" != parked ] && [ "$current_state" != blocked ]; } \
|
|
517
|
+
|| { [ "$current_state" = "done" ] || [ "$current_state" = "failed" ]; }; }; then
|
|
518
|
+
open_decisions_tsv=""
|
|
519
|
+
fi
|
|
520
|
+
open_decisions_json=$(printf '%s' "$open_decisions_tsv" | jq -R -s '
|
|
521
|
+
[ splits("\n") | select(length > 0)
|
|
522
|
+
| (capture("^(?<key>[^\t]*)\t(?<verb>[^\t]*)\t(?<summary>.*)$")?)
|
|
523
|
+
| select(. != null) ]')
|
|
524
|
+
pending_decision=$(printf '%s' "$open_decisions_json" | jq 'if any(.[]; .verb == "needs-decision") then 1 else 0 end')
|
|
525
|
+
blocked_event=$(printf '%s' "$open_decisions_json" | jq 'if any(.[]; .verb == "blocked") then 1 else 0 end')
|
|
526
|
+
|
|
527
|
+
endpoint_exists=null
|
|
528
|
+
agent_alive=not_checked
|
|
529
|
+
if [ -n "$remote_host" ]; then
|
|
530
|
+
if remote_state=$(fm_run_timed "$FM_SNAPSHOT_SECONDMATE_TIMEOUT" \
|
|
531
|
+
"$SCRIPT_DIR/fm-on.sh" "$id" fm-remote-secondmate-control.sh state "$id" < /dev/null 2>/dev/null); then
|
|
532
|
+
remote_rc=0
|
|
533
|
+
else
|
|
534
|
+
remote_rc=$?
|
|
535
|
+
fi
|
|
536
|
+
if [ "$remote_rc" -eq 0 ]; then
|
|
537
|
+
remote_home_present=true
|
|
538
|
+
remote_state=$(printf '%s\n' "$remote_state" | tail -1)
|
|
539
|
+
case "$remote_state" in
|
|
540
|
+
alive) endpoint_exists=true; agent_alive=alive ;;
|
|
541
|
+
dead) endpoint_exists=true; agent_alive=dead ;;
|
|
542
|
+
missing) endpoint_exists=false; agent_alive=dead ;;
|
|
543
|
+
*) endpoint_exists=null; agent_alive=unknown ;;
|
|
544
|
+
esac
|
|
545
|
+
else
|
|
546
|
+
endpoint_exists=null
|
|
547
|
+
agent_alive=unknown
|
|
548
|
+
fi
|
|
549
|
+
else
|
|
550
|
+
if [ -n "$target" ]; then
|
|
551
|
+
if fm_backend_target_exists "$backend" "$target" "fm-$id" >/dev/null 2>&1; then
|
|
552
|
+
endpoint_exists=true
|
|
553
|
+
else
|
|
554
|
+
endpoint_exists=false
|
|
555
|
+
fi
|
|
556
|
+
fi
|
|
557
|
+
if [ "$kind" = secondmate ] && [ -n "$target" ]; then
|
|
558
|
+
agent_alive=$(fm_backend_agent_alive "$backend" "$target" 2>/dev/null || printf unknown)
|
|
559
|
+
fi
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
[ -f "$report_path" ] && report_present=1 || report_present=0
|
|
563
|
+
meta_json=$(path_present_json "$meta")
|
|
564
|
+
status_json=$event_json
|
|
565
|
+
report_json=$(path_present_json "$report_path")
|
|
566
|
+
if [ -n "$worktree" ]; then worktree_json=$(path_present_json "$worktree"); else worktree_json=$(jq -n '{path:null,present:false}'); fi
|
|
567
|
+
if [ -n "$home" ] && [ -n "$remote_host" ]; then
|
|
568
|
+
home_json=$(jq -n --arg path "$home" --argjson present "$remote_home_present" '{path:$path,present:$present}')
|
|
569
|
+
elif [ -n "$home" ]; then
|
|
570
|
+
home_json=$(path_present_json "$home")
|
|
571
|
+
else
|
|
572
|
+
home_json=$(jq -n '{path:null,present:false}')
|
|
573
|
+
fi
|
|
574
|
+
|
|
575
|
+
jq -n \
|
|
576
|
+
--arg id "$id" \
|
|
577
|
+
--arg kind "$kind" \
|
|
578
|
+
--arg harness "$harness" \
|
|
579
|
+
--arg mode "$mode" \
|
|
580
|
+
--arg yolo "$yolo" \
|
|
581
|
+
--arg project "$project" \
|
|
582
|
+
--arg worktree "$worktree" \
|
|
583
|
+
--arg home "$home" \
|
|
584
|
+
--arg projects "$projects" \
|
|
585
|
+
--arg spawn_gen "$spawn_gen" \
|
|
586
|
+
--arg backend "$backend" \
|
|
587
|
+
--arg target "$target" \
|
|
588
|
+
--arg remote_host "$remote_host" \
|
|
589
|
+
--arg remote_root "$remote_root" \
|
|
590
|
+
--arg pr "$pr" \
|
|
591
|
+
--arg pr_source "$pr_source" \
|
|
592
|
+
--arg agent_alive "$agent_alive" \
|
|
593
|
+
--arg observed_at "$SNAPSHOT_NOW" \
|
|
594
|
+
--arg last_event_raw "$last_event_raw" \
|
|
595
|
+
--argjson current_state "$current_json" \
|
|
596
|
+
--argjson meta_path "$meta_json" \
|
|
597
|
+
--argjson status_log "$status_json" \
|
|
598
|
+
--argjson report "$report_json" \
|
|
599
|
+
--argjson worktree_path "$worktree_json" \
|
|
600
|
+
--argjson home_path "$home_json" \
|
|
601
|
+
--argjson endpoint_exists "$endpoint_exists" \
|
|
602
|
+
--argjson open_decisions "$open_decisions_json" \
|
|
603
|
+
--argjson pending_decision "$(bool_json "$pending_decision")" \
|
|
604
|
+
--argjson blocked_event "$(bool_json "$blocked_event")" \
|
|
605
|
+
--argjson report_present "$(bool_json "$report_present")" \
|
|
606
|
+
'{
|
|
607
|
+
id:$id,
|
|
608
|
+
kind:$kind,
|
|
609
|
+
harness:($harness // ""),
|
|
610
|
+
mode:($mode // ""),
|
|
611
|
+
yolo:($yolo // ""),
|
|
612
|
+
project:($project // ""),
|
|
613
|
+
spawn_gen:($spawn_gen | if . == "" then null else . end),
|
|
614
|
+
backend:$backend,
|
|
615
|
+
remote:(if $remote_host == "" then null else {host:$remote_host,root:$remote_root} end),
|
|
616
|
+
paths:{
|
|
617
|
+
meta:$meta_path,
|
|
618
|
+
status_log:$status_log,
|
|
619
|
+
worktree:$worktree_path,
|
|
620
|
+
home:$home_path,
|
|
621
|
+
report:$report
|
|
622
|
+
},
|
|
623
|
+
secondmate_projects:($projects | if . == "" then [] else split(",") | map(gsub("^[[:space:]]+|[[:space:]]+$"; "")) | map(select(. != "")) end),
|
|
624
|
+
current_state:($current_state + {observed_at:$observed_at,freshness:"fresh"}),
|
|
625
|
+
endpoint:{target:($target | if . == "" then null else . end),exists:$endpoint_exists,agent_alive:$agent_alive,
|
|
626
|
+
status:(if $endpoint_exists == false then "absent"
|
|
627
|
+
elif $agent_alive == "alive" or $agent_alive == "dead" then $agent_alive
|
|
628
|
+
else "unknown" end),
|
|
629
|
+
observed_at:$observed_at,freshness:"fresh"},
|
|
630
|
+
pr:{url:($pr | if . == "" then null else . end),source:$pr_source},
|
|
631
|
+
hints:{
|
|
632
|
+
pending_decision:$pending_decision,
|
|
633
|
+
blocked_event:$blocked_event,
|
|
634
|
+
open_decisions:$open_decisions,
|
|
635
|
+
scout_report_present:$report_present,
|
|
636
|
+
last_event_text:$last_event_raw
|
|
637
|
+
},
|
|
638
|
+
actions:(
|
|
639
|
+
if $kind == "secondmate" then
|
|
640
|
+
{send:"bin/fm-send.sh fm-\($id) \u0027<request>\u0027",
|
|
641
|
+
watch:"read status/doc return channel; do not routinely fm-peek a secondmate for answers",
|
|
642
|
+
return_channel_note:"Secondmate answers come back through status/doc paths after a marked fm-send request."}
|
|
643
|
+
else
|
|
644
|
+
{watch:"bin/fm-peek.sh fm-\($id)",
|
|
645
|
+
steer:"bin/fm-send.sh fm-\($id) \u0027<instruction>\u0027",
|
|
646
|
+
return_channel_note:null}
|
|
647
|
+
end)
|
|
648
|
+
}'
|
|
649
|
+
done | jq -s 'sort_by(.id)'
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
# Main-home current-inventory validity: same orphan / unstructured-current checks
|
|
653
|
+
# used by secondmate_home_summary_json, without inventing live task rows.
|
|
654
|
+
# Meta inventory remains the sole source of live workers; this object only
|
|
655
|
+
# discloses backlog↔task inconsistency for renderers (Bearings omitted/gates).
|
|
656
|
+
main_inventory_json() { # <backlog-json> <tasks-json>
|
|
657
|
+
jq -n \
|
|
658
|
+
--argjson backlog "$1" \
|
|
659
|
+
--argjson tasks "$2" '
|
|
660
|
+
([ $backlog.records[]?
|
|
661
|
+
| select((.state == "in_flight" or .state == "queued") and (.structured | not)) ]) as $unstructured_current
|
|
662
|
+
| ([ $backlog.records[]?
|
|
663
|
+
| select(.state == "in_flight" and .structured and .requires_child_metadata) ]) as $owned_in_flight
|
|
664
|
+
| ([ $owned_in_flight[]
|
|
665
|
+
| select(.id as $id | [$tasks[].id] | index($id) | not)
|
|
666
|
+
| .id ]) as $orphan_in_flight
|
|
667
|
+
| (($unstructured_current | length) == 0
|
|
668
|
+
and ($orphan_in_flight | length) == 0) as $valid
|
|
669
|
+
| (if ($unstructured_current | length) > 0 then "unstructured current backlog row"
|
|
670
|
+
elif ($orphan_in_flight | length) > 0 then "in-flight backlog item has no child metadata"
|
|
671
|
+
else null end) as $reason
|
|
672
|
+
| {
|
|
673
|
+
valid:$valid,
|
|
674
|
+
reason:$reason,
|
|
675
|
+
orphan_in_flight:$orphan_in_flight,
|
|
676
|
+
unstructured_current_count:($unstructured_current | length)
|
|
677
|
+
}'
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# Project one home's canonical structured inventory into the bounded shape a
|
|
681
|
+
# validated parent read needs.
|
|
682
|
+
# This mode never reads parent events or terminal text and never aggregates
|
|
683
|
+
# nested secondmates.
|
|
684
|
+
secondmate_home_summary_json() { # <backlog-json> <tasks-json>
|
|
685
|
+
jq -n \
|
|
686
|
+
--arg generated "$SNAPSHOT_NOW" \
|
|
687
|
+
--argjson generated_epoch "$SNAPSHOT_EPOCH" \
|
|
688
|
+
--arg home "$FM_HOME" \
|
|
689
|
+
--argjson child_n "$FM_SNAPSHOT_SECONDMATE_CHILDREN" \
|
|
690
|
+
--argjson queued_n "$FM_SNAPSHOT_SECONDMATE_QUEUED" \
|
|
691
|
+
--argjson decisions_n "$FM_SNAPSHOT_SECONDMATE_DECISIONS" \
|
|
692
|
+
--argjson landed_n "$FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME" \
|
|
693
|
+
--argjson backlog "$1" \
|
|
694
|
+
--argjson tasks "$2" '
|
|
695
|
+
def trunc($n):
|
|
696
|
+
tostring | gsub("\\s+"; " ")
|
|
697
|
+
| if length > $n then .[:$n] + "…" else . end;
|
|
698
|
+
([ $backlog.records[]?
|
|
699
|
+
| select((.state == "in_flight" or .state == "queued") and (.structured | not)) ]) as $unstructured_current
|
|
700
|
+
| ([ $backlog.records[]? | select(.state == "in_flight" and .structured) ]) as $owned_in_flight
|
|
701
|
+
| ([ $backlog.records[]?
|
|
702
|
+
| select(.structured and
|
|
703
|
+
(.state == "queued" or
|
|
704
|
+
(.state == "in_flight" and .current_role == "held"
|
|
705
|
+
and (.id as $id
|
|
706
|
+
| any($tasks[]; .id == $id and .current_state.state == "working") | not)))) ]) as $queued_all
|
|
707
|
+
| ([ $queued_all[]
|
|
708
|
+
| select(.captain_actionable == true)
|
|
709
|
+
| {id,key:.id,verb:"captain-hold",summary:(.title | trunc(160)),
|
|
710
|
+
reason:(.hold_reason | trunc(160)),
|
|
711
|
+
hold_until:(.hold_until // null),
|
|
712
|
+
deferred_marker:(.deferred_marker // false),source:"backlog"} ]) as $captain_holds_all
|
|
713
|
+
| ([ $backlog.records[]? | select(.state == "done" and .structured and .hold_kind != "captain")
|
|
714
|
+
| {id:(.id | trunc(120)),title:(.title | trunc(120)),
|
|
715
|
+
pr_url:((.pr_url // null) | if . == null then null else trunc(500) end),
|
|
716
|
+
report_path:((.report_path // null) | if . == null then null else trunc(500) end),
|
|
717
|
+
local_note:((.local_note // null) | if . == null then null else trunc(120) end),completion} ]
|
|
718
|
+
| sort_by([(.completion.date // ""), .id]) | reverse) as $landed_all
|
|
719
|
+
| ([ $tasks[] | select(.current_state.state == "unknown") ]) as $unknown_children
|
|
720
|
+
| ([ $owned_in_flight[]
|
|
721
|
+
| select(.requires_child_metadata)
|
|
722
|
+
| select(.id as $id | [$tasks[].id] | index($id) | not) ]) as $orphan_in_flight
|
|
723
|
+
| ([ $tasks[]
|
|
724
|
+
| select(.id as $id | [$owned_in_flight[].id] | index($id) | not)
|
|
725
|
+
| {id,state:.current_state.state} ]) as $unowned_children
|
|
726
|
+
| ([ $owned_in_flight[] as $work
|
|
727
|
+
| $tasks[]
|
|
728
|
+
| select(.id == $work.id and (.current_state.state == "done" or .current_state.state == "failed"))
|
|
729
|
+
| {id,state:.current_state.state} ]) as $terminal_in_flight
|
|
730
|
+
| ([if $backlog.present != true then
|
|
731
|
+
{kind:"missing_backlog",ids:[],reason:"missing structured backlog"}
|
|
732
|
+
else empty end,
|
|
733
|
+
if ($unstructured_current | length) > 0 then
|
|
734
|
+
{kind:"unstructured_current",ids:[],reason:"unstructured current backlog row"}
|
|
735
|
+
else empty end,
|
|
736
|
+
if ($orphan_in_flight | length) > 0 then
|
|
737
|
+
{kind:"orphan_in_flight",ids:($orphan_in_flight | map(.id)),
|
|
738
|
+
reason:("in-flight backlog item has no child metadata: " + ($orphan_in_flight | map(.id) | join(", ")))}
|
|
739
|
+
else empty end,
|
|
740
|
+
if ($unowned_children | length) > 0 then
|
|
741
|
+
{kind:"unowned_current",ids:($unowned_children | map(.id)),
|
|
742
|
+
reason:("live child state has no in-flight backlog item: " +
|
|
743
|
+
($unowned_children | map(.id + "=" + .state) | join(", ")))}
|
|
744
|
+
else empty end,
|
|
745
|
+
if ($terminal_in_flight | length) > 0 then
|
|
746
|
+
{kind:"terminal_in_flight",ids:($terminal_in_flight | map(.id)),
|
|
747
|
+
reason:("in-flight backlog item has terminal child state: " +
|
|
748
|
+
($terminal_in_flight | map(.id + "=" + .state) | join(", ")))}
|
|
749
|
+
else empty end]) as $strict_invalidities
|
|
750
|
+
| ([ $owned_in_flight[] as $work
|
|
751
|
+
| select($work.current_role != "program")
|
|
752
|
+
| $tasks[]
|
|
753
|
+
| select(.id == $work.id and .current_state.state == "working")
|
|
754
|
+
| {id,kind,state:.current_state.state,source:.current_state.source,
|
|
755
|
+
doing:((.current_state.detail // "") | trunc(120))} ]) as $active_all
|
|
756
|
+
| ($captain_holds_all
|
|
757
|
+
+ ([ $tasks[] as $t | ($t.hints.open_decisions // [])[]
|
|
758
|
+
| {id:$t.id,key,verb,summary:(.summary | trunc(160)),reason:null,source:"status"} ])) as $decisions_all
|
|
759
|
+
| ([ $queued_all[]
|
|
760
|
+
| select((.unresolved_blocker_ids | length) > 0 or (.hold_reason != null and .hold_kind != null))
|
|
761
|
+
| {id:(.id | trunc(120)),title:(.title | trunc(90)),
|
|
762
|
+
blocked_by:((.unresolved_blocker_ids | join(",")) | if . == "" then null else trunc(120) end),
|
|
763
|
+
blocked_by_ids:(.blocked_by_ids | map(trunc(120))),
|
|
764
|
+
unresolved_blocker_ids:(.unresolved_blocker_ids | map(trunc(120))),
|
|
765
|
+
reason:((.hold_reason // .blocked_reason // "blocked") | trunc(120)),source:"backlog"} ]
|
|
766
|
+
+ [ $owned_in_flight[] as $work
|
|
767
|
+
| $tasks[]
|
|
768
|
+
| select(.id == $work.id and (.current_state.state == "parked" or .current_state.state == "paused" or .current_state.state == "blocked"))
|
|
769
|
+
| select(($work.hold_reason != null and $work.hold_kind != null) | not)
|
|
770
|
+
| {id,title:((.backlog.title // .id) | trunc(90)),blocked_by:null,
|
|
771
|
+
blocked_by_ids:[],unresolved_blocker_ids:[],
|
|
772
|
+
reason:((.current_state.detail // .current_state.state) | trunc(120)),source:"child-state"} ]) as $holds_all
|
|
773
|
+
| ($backlog.present == true
|
|
774
|
+
and ($unstructured_current | length) == 0
|
|
775
|
+
and ($unknown_children | length) == 0
|
|
776
|
+
and ($orphan_in_flight | length) == 0
|
|
777
|
+
and ($unowned_children | length) == 0
|
|
778
|
+
and ($terminal_in_flight | length) == 0) as $valid
|
|
779
|
+
| (if ($strict_invalidities | length) > 0 then $strict_invalidities[0].reason
|
|
780
|
+
elif ($unknown_children | length) > 0 then
|
|
781
|
+
"child current state unavailable: " + ($unknown_children | map(.id) | join(", "))
|
|
782
|
+
else null end) as $reason
|
|
783
|
+
| (if ($strict_invalidities | length) > 0 then $strict_invalidities[0] | del(.reason)
|
|
784
|
+
elif ($unknown_children | length) > 0 then {kind:"child_current_unavailable",ids:($unknown_children | map(.id))}
|
|
785
|
+
else {kind:null,ids:[]} end) as $invalidity
|
|
786
|
+
| (if ($valid | not)
|
|
787
|
+
and (($unknown_children | length) > 0
|
|
788
|
+
or (["orphan_in_flight","unowned_current","terminal_in_flight"]
|
|
789
|
+
| index($invalidity.kind) | not))
|
|
790
|
+
then "unknown"
|
|
791
|
+
elif any($decisions_all[]; .verb == "needs-decision" or .verb == "captain-hold") then "captain_decision"
|
|
792
|
+
elif ($active_all | length) > 0 then "active_child_work"
|
|
793
|
+
elif ($holds_all | length) > 0 then "externally_held"
|
|
794
|
+
else "no_active_work" end) as $state
|
|
795
|
+
| {
|
|
796
|
+
schema:"fm-secondmate-home-summary.v1",
|
|
797
|
+
generated:$generated,
|
|
798
|
+
generated_epoch:$generated_epoch,
|
|
799
|
+
home:$home,
|
|
800
|
+
valid:$valid,
|
|
801
|
+
reason:$reason,
|
|
802
|
+
invalidity:$invalidity,
|
|
803
|
+
state:$state,
|
|
804
|
+
active_children:$active_all[:$child_n],
|
|
805
|
+
decisions_open:$decisions_all[:$decisions_n],
|
|
806
|
+
holds:$holds_all[:$queued_n],
|
|
807
|
+
queued:([$queued_all[] | {id:(.id | trunc(120)),title:(.title | trunc(120)),
|
|
808
|
+
blocked_by:((.blocked_by // null) | if . == null then null else trunc(120) end),
|
|
809
|
+
blocked_by_ids:((.blocked_by_ids // []) | map(trunc(120))),
|
|
810
|
+
unresolved_blocker_ids:((.unresolved_blocker_ids // []) | map(trunc(120))),
|
|
811
|
+
blocked_reason:((.blocked_reason // null) | if . == null then null else trunc(160) end),
|
|
812
|
+
hold_reason:((.hold_reason // null) | if . == null then null else trunc(160) end),
|
|
813
|
+
hold_kind:((.hold_kind // null) | if . == null then null else trunc(40) end),
|
|
814
|
+
hold_until:((.hold_until // null) | if . == null then null else trunc(40) end),
|
|
815
|
+
deferred_marker:(.deferred_marker // false),
|
|
816
|
+
captain_actionable:(.captain_actionable // false),
|
|
817
|
+
repo:((.repo // null) | if . == null then null else trunc(120) end),
|
|
818
|
+
kind:((.kind // null) | if . == null then null else trunc(40) end)}][:$queued_n]),
|
|
819
|
+
landed:(if $landed_n == 0 then $landed_all else $landed_all[:$landed_n] end),
|
|
820
|
+
endpoints:([$tasks[] | {id,state:.current_state.state,source:.current_state.source,
|
|
821
|
+
endpoint:(.endpoint + {target:((.endpoint.target // null) | if . == null then null else trunc(240) end)})}][:$child_n]),
|
|
822
|
+
counts:{
|
|
823
|
+
active_children:($active_all | length),
|
|
824
|
+
decisions_open:($decisions_all | length),
|
|
825
|
+
holds:($holds_all | length),
|
|
826
|
+
queued:($queued_all | length),
|
|
827
|
+
landed:($landed_all | length),
|
|
828
|
+
endpoints:($tasks | length)
|
|
829
|
+
},
|
|
830
|
+
omitted:[
|
|
831
|
+
(if ($active_all | length) > $child_n then {surface:"active_children",count:(($active_all | length) - $child_n)} else empty end),
|
|
832
|
+
(if ($decisions_all | length) > $decisions_n then {surface:"decisions_open",count:(($decisions_all | length) - $decisions_n)} else empty end),
|
|
833
|
+
(if ($queued_all | length) > $queued_n then {surface:"queued",count:(($queued_all | length) - $queued_n)} else empty end),
|
|
834
|
+
(if ($tasks | length) > $child_n then {surface:"endpoints",count:(($tasks | length) - $child_n)} else empty end),
|
|
835
|
+
(if $landed_n > 0 and ($landed_all | length) > $landed_n then {surface:"landed",count:(($landed_all | length) - $landed_n)} else empty end)
|
|
836
|
+
]
|
|
837
|
+
}'
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
# Current registered-secondmate aggregation.
|
|
841
|
+
# The validated home summary is canonical.
|
|
842
|
+
# Parent status and bounded terminal capture remain untrusted supplemental evidence
|
|
843
|
+
# with explicit provenance, and can only produce a contradiction or unknown fallback.
|
|
844
|
+
FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME=${FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME:-10}
|
|
845
|
+
case "$FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME" in ''|*[!0-9]*) FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME=10 ;; esac
|
|
846
|
+
|
|
847
|
+
# GNU stat treats -f as a filesystem-report command, so a BSD-first fallback can
|
|
848
|
+
# pollute arithmetic input before failing. Select the platform syntax once.
|
|
849
|
+
if [ "$(uname 2>/dev/null || true)" = Darwin ]; then
|
|
850
|
+
SNAPSHOT_STAT_STYLE=bsd
|
|
851
|
+
file_mtime_epoch() { stat -f '%m' "$1" 2>/dev/null || true; }
|
|
852
|
+
file_mode_octal() { stat -f '%Lp' "$1" 2>/dev/null || true; }
|
|
853
|
+
else
|
|
854
|
+
SNAPSHOT_STAT_STYLE=gnu
|
|
855
|
+
file_mtime_epoch() { stat -c '%Y' "$1" 2>/dev/null || true; }
|
|
856
|
+
file_mode_octal() { stat -c '%a' "$1" 2>/dev/null || true; }
|
|
857
|
+
fi
|
|
858
|
+
|
|
859
|
+
registry_secondmates_json() {
|
|
860
|
+
local reg="$DATA/secondmates.md" out rc reason mode script parse_filter output_filter
|
|
861
|
+
if [ ! -f "$reg" ]; then
|
|
862
|
+
jq -n --arg path "$reg" --arg observed "$SNAPSHOT_NOW" \
|
|
863
|
+
'{present:false,available:true,complete:true,reason:null,provenance:"registered-table",path:$path,freshness:{status:"fresh",observed_at:$observed},records:[],input_truncated:false,records_truncated:false,reasons:[],lines_in_window:0,records_in_window:0}'
|
|
864
|
+
return 0
|
|
865
|
+
fi
|
|
866
|
+
mode=$(file_mode_octal "$reg")
|
|
867
|
+
if [ -z "$mode" ] || [ $((8#$mode & 0444)) -eq 0 ]; then
|
|
868
|
+
jq -n --arg path "$reg" --arg observed "$SNAPSHOT_NOW" \
|
|
869
|
+
--arg reason "registered secondmate table is unreadable" \
|
|
870
|
+
'{present:true,available:false,complete:false,reason:$reason,provenance:"registered-table",path:$path,freshness:{status:"unavailable",observed_at:$observed},records:[],input_truncated:false,records_truncated:false,reasons:[$reason],lines_in_window:0,records_in_window:0}'
|
|
871
|
+
return 0
|
|
872
|
+
fi
|
|
873
|
+
script=$(cat <<'BASH'
|
|
874
|
+
f=$1
|
|
875
|
+
max_lines=$2
|
|
876
|
+
max_bytes=$3
|
|
877
|
+
max_records=$4
|
|
878
|
+
path=$5
|
|
879
|
+
observed=$6
|
|
880
|
+
parse_filter=$7
|
|
881
|
+
output_filter=$8
|
|
882
|
+
content=$(LC_ALL=C head -c "$((max_bytes + 1))" "$f" || exit 3; printf "\036") || exit 3
|
|
883
|
+
content=${content%$'\036'}
|
|
884
|
+
bytes=$(printf "%s" "$content" | LC_ALL=C wc -c | tr -d " ")
|
|
885
|
+
byte_truncated=false
|
|
886
|
+
if [ "$bytes" -gt "$max_bytes" ]; then
|
|
887
|
+
byte_truncated=true
|
|
888
|
+
content=$(printf "%s" "$content" | LC_ALL=C head -c "$max_bytes")
|
|
889
|
+
complete=${content%$'\n'*}
|
|
890
|
+
if [ "$complete" != "$content" ]; then
|
|
891
|
+
content=$complete
|
|
892
|
+
else
|
|
893
|
+
content=
|
|
894
|
+
fi
|
|
895
|
+
fi
|
|
896
|
+
if [ -n "$content" ]; then
|
|
897
|
+
lines=$(printf "%s\n" "$content" | awk "END {print NR}")
|
|
898
|
+
else
|
|
899
|
+
lines=0
|
|
900
|
+
fi
|
|
901
|
+
line_truncated=false
|
|
902
|
+
if [ "$lines" -gt "$max_lines" ]; then line_truncated=true; fi
|
|
903
|
+
window=$(printf "%s\n" "$content" | LC_ALL=C head -n "$max_lines") || exit 3
|
|
904
|
+
if [ -n "$window" ]; then
|
|
905
|
+
lines_in_window=$(printf "%s\n" "$window" | awk "END {print NR}")
|
|
906
|
+
else
|
|
907
|
+
lines_in_window=0
|
|
908
|
+
fi
|
|
909
|
+
records=$(printf "%s\n" "$window" | jq -Rn "$parse_filter") || exit 3
|
|
910
|
+
records_in_window=$(printf "%s" "$records" | jq "length") || exit 3
|
|
911
|
+
records_truncated=false
|
|
912
|
+
if [ "$records_in_window" -gt "$max_records" ]; then records_truncated=true; fi
|
|
913
|
+
printf "%s" "$records" | jq \
|
|
914
|
+
--arg path "$path" --arg observed "$observed" \
|
|
915
|
+
--argjson byte_truncated "$byte_truncated" \
|
|
916
|
+
--argjson line_truncated "$line_truncated" \
|
|
917
|
+
--argjson records_truncated "$records_truncated" \
|
|
918
|
+
--argjson lines_in_window "$lines_in_window" \
|
|
919
|
+
--argjson records_in_window "$records_in_window" \
|
|
920
|
+
--argjson max_records "$max_records" "$output_filter"
|
|
921
|
+
BASH
|
|
922
|
+
)
|
|
923
|
+
parse_filter=$(cat <<'JQ'
|
|
924
|
+
[ inputs
|
|
925
|
+
| select(startswith("- "))
|
|
926
|
+
| (capture("^- (?<id>[^[:space:]]+)")?) as $id
|
|
927
|
+
| select($id != null)
|
|
928
|
+
| ([capture("^.*\\(host:[[:space:]]*(?<host>[^;)]*);[[:space:]]*root:[[:space:]]*(?<root>[^;)]*);[[:space:]]*home:[[:space:]]*(?<home>[^;)]*);[[:space:]]*scope:[[:space:]]*.*;[[:space:]]*projects:[[:space:]]*[^;)]*;[[:space:]]*added[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}\\)[[:space:]]*$")?][0] // null) as $remote
|
|
929
|
+
| ([capture("^.*\\(home:[[:space:]]*(?<home>[^;)]*);[[:space:]]*scope:[[:space:]]*.*;[[:space:]]*projects:[[:space:]]*[^;)]*;[[:space:]]*added[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}\\)[[:space:]]*$")?][0] // null) as $local
|
|
930
|
+
| ($local // $remote) as $route
|
|
931
|
+
| (($local == null) and ($remote != null)) as $is_remote
|
|
932
|
+
| {id:$id.id,home:($route.home // null),host:(if $is_remote then $remote.host else null end),root:(if $is_remote then $remote.root else null end),
|
|
933
|
+
remote:$is_remote,registered:true,
|
|
934
|
+
registry_error:(if $route == null or ($route.home | length) == 0 then "registry entry has no home" else null end)} ]
|
|
935
|
+
| group_by(.id)
|
|
936
|
+
| map(if length > 1 then .[0] + {registry_error:"duplicate secondmate id in registry"} else .[0] end)
|
|
937
|
+
JQ
|
|
938
|
+
)
|
|
939
|
+
output_filter=$(cat <<'JQ'
|
|
940
|
+
{present:true,available:true,reason:null,provenance:"registered-table",path:$path,
|
|
941
|
+
freshness:{status:"fresh",observed_at:$observed},
|
|
942
|
+
records:(if length > $max_records then .[:$max_records] else . end),
|
|
943
|
+
input_truncated:($byte_truncated or $line_truncated),records_truncated:$records_truncated,
|
|
944
|
+
complete:(($byte_truncated or $line_truncated or $records_truncated) | not),
|
|
945
|
+
reasons:[
|
|
946
|
+
(if $byte_truncated then "byte_limit" else empty end),
|
|
947
|
+
(if $line_truncated then "line_limit" else empty end),
|
|
948
|
+
(if $records_truncated then "record_limit" else empty end)
|
|
949
|
+
],lines_in_window:$lines_in_window,records_in_window:$records_in_window}
|
|
950
|
+
JQ
|
|
951
|
+
)
|
|
952
|
+
out=$(fm_run_timed "$FM_SNAPSHOT_REGISTRY_TIMEOUT" bash -c "$script" \
|
|
953
|
+
fm-secondmate-registry "$reg" "$FM_SNAPSHOT_REGISTRY_LINES" \
|
|
954
|
+
"$FM_SNAPSHOT_REGISTRY_BYTES" "$FM_SNAPSHOT_REGISTRY_RECORDS" "$reg" "$SNAPSHOT_NOW" \
|
|
955
|
+
"$parse_filter" "$output_filter" 2>/dev/null)
|
|
956
|
+
rc=$?
|
|
957
|
+
if [ "$rc" -eq 0 ] && printf '%s' "$out" | jq -e '
|
|
958
|
+
.available == true and (.records | type) == "array"
|
|
959
|
+
' >/dev/null 2>&1; then
|
|
960
|
+
printf '%s' "$out"
|
|
961
|
+
return 0
|
|
962
|
+
fi
|
|
963
|
+
[ "$rc" -eq 124 ] && reason="registered secondmate table read timed out" \
|
|
964
|
+
|| reason="registered secondmate table is unreadable"
|
|
965
|
+
jq -n --arg path "$reg" --arg observed "$SNAPSHOT_NOW" --arg reason "$reason" \
|
|
966
|
+
'{present:true,available:false,complete:false,reason:$reason,provenance:"registered-table",path:$path,freshness:{status:"unavailable",observed_at:$observed},records:[],input_truncated:false,records_truncated:false,reasons:[$reason],lines_in_window:0,records_in_window:0}'
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
bounded_parent_activities_json() { # <status-file>
|
|
970
|
+
local f=$1 out rc reason script
|
|
971
|
+
if [ ! -f "$f" ]; then
|
|
972
|
+
jq -n '{records:[],available:true,input_truncated:false,retained_truncated:false,reasons:[],lines_in_window:0,records_in_window:0}'
|
|
973
|
+
return 0
|
|
974
|
+
fi
|
|
975
|
+
script=$(cat <<'BASH'
|
|
976
|
+
classify=$1
|
|
977
|
+
f=$2
|
|
978
|
+
max_lines=$3
|
|
979
|
+
max_bytes=$4
|
|
980
|
+
max_records=$5
|
|
981
|
+
stat_style=$6
|
|
982
|
+
. "$classify"
|
|
983
|
+
if [ "$stat_style" = bsd ]; then
|
|
984
|
+
size=$(stat -f "%z" "$f" 2>/dev/null) || exit 3
|
|
985
|
+
else
|
|
986
|
+
size=$(stat -c "%s" "$f" 2>/dev/null) || exit 3
|
|
987
|
+
fi
|
|
988
|
+
content=$(LC_ALL=C tail -c "$max_bytes" "$f") || exit 3
|
|
989
|
+
byte_truncated=false
|
|
990
|
+
if [ "$size" -gt "$max_bytes" ]; then
|
|
991
|
+
byte_truncated=true
|
|
992
|
+
complete=${content#*$'\n'}
|
|
993
|
+
if [ "$complete" != "$content" ]; then
|
|
994
|
+
content=$complete
|
|
995
|
+
else
|
|
996
|
+
content=
|
|
997
|
+
fi
|
|
998
|
+
fi
|
|
999
|
+
if [ -n "$content" ]; then
|
|
1000
|
+
lines_in_chunk=$(printf "%s\n" "$content" | awk "END {print NR}")
|
|
1001
|
+
else
|
|
1002
|
+
lines_in_chunk=0
|
|
1003
|
+
fi
|
|
1004
|
+
line_truncated=false
|
|
1005
|
+
if [ "$lines_in_chunk" -gt "$max_lines" ]; then line_truncated=true; fi
|
|
1006
|
+
window=$(printf "%s\n" "$content" | LC_ALL=C tail -n "$max_lines") || exit 3
|
|
1007
|
+
if [ -n "$window" ]; then
|
|
1008
|
+
lines_in_window=$(printf "%s\n" "$window" | awk "END {print NR}")
|
|
1009
|
+
else
|
|
1010
|
+
lines_in_window=0
|
|
1011
|
+
fi
|
|
1012
|
+
records=$(printf "%s\n" "$window" | status_open_activities - \
|
|
1013
|
+
| jq -R -s '[splits("\n") | select(length > 0)
|
|
1014
|
+
| (capture("^(?<key>[^\t]*)\t(?<verb>[^\t]*)\t(?<summary>.*)$")?)
|
|
1015
|
+
| select(. != null)]') || exit 3
|
|
1016
|
+
records_in_window=$(printf "%s" "$records" | jq "length") || exit 3
|
|
1017
|
+
retained_truncated=false
|
|
1018
|
+
if [ "$records_in_window" -gt "$max_records" ]; then retained_truncated=true; fi
|
|
1019
|
+
printf "%s" "$records" | jq \
|
|
1020
|
+
--argjson byte_truncated "$byte_truncated" \
|
|
1021
|
+
--argjson line_truncated "$line_truncated" \
|
|
1022
|
+
--argjson retained_truncated "$retained_truncated" \
|
|
1023
|
+
--argjson lines_in_window "$lines_in_window" \
|
|
1024
|
+
--argjson records_in_window "$records_in_window" \
|
|
1025
|
+
--argjson max_records "$max_records" '
|
|
1026
|
+
{records:(if length > $max_records then .[-$max_records:] else . end),
|
|
1027
|
+
available:true,
|
|
1028
|
+
input_truncated:($byte_truncated or $line_truncated),
|
|
1029
|
+
retained_truncated:$retained_truncated,
|
|
1030
|
+
reasons:[
|
|
1031
|
+
(if $byte_truncated then "byte_limit" else empty end),
|
|
1032
|
+
(if $line_truncated then "line_limit" else empty end),
|
|
1033
|
+
(if $retained_truncated then "activity_limit" else empty end)
|
|
1034
|
+
],
|
|
1035
|
+
lines_in_window:$lines_in_window,
|
|
1036
|
+
records_in_window:$records_in_window}'
|
|
1037
|
+
BASH
|
|
1038
|
+
)
|
|
1039
|
+
out=$(fm_run_timed "$FM_SNAPSHOT_PARENT_ACTIVITY_TIMEOUT" bash -c "$script" \
|
|
1040
|
+
fm-parent-activities "$SCRIPT_DIR/fm-classify-lib.sh" "$f" \
|
|
1041
|
+
"$FM_SNAPSHOT_PARENT_ACTIVITY_LINES" "$FM_SNAPSHOT_PARENT_ACTIVITY_BYTES" \
|
|
1042
|
+
"$FM_SNAPSHOT_PARENT_ACTIVITIES" "$SNAPSHOT_STAT_STYLE" 2>/dev/null)
|
|
1043
|
+
rc=$?
|
|
1044
|
+
if [ "$rc" -eq 0 ] && printf '%s' "$out" | jq -e '
|
|
1045
|
+
(.records | type) == "array" and (.available | type) == "boolean"
|
|
1046
|
+
' >/dev/null 2>&1; then
|
|
1047
|
+
printf '%s' "$out"
|
|
1048
|
+
return 0
|
|
1049
|
+
fi
|
|
1050
|
+
[ "$rc" -eq 124 ] && reason="timeout" || reason="read_failed"
|
|
1051
|
+
jq -n --arg reason "$reason" \
|
|
1052
|
+
'{records:[],available:false,input_truncated:false,retained_truncated:false,reasons:[$reason],lines_in_window:0,records_in_window:0}'
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
terminal_evidence_json() { # <parent-task-json> <event-note> <evidence-contradicts>
|
|
1056
|
+
local task=$1 note=$2 evidence_contradicts=$3 backend target exists expected out rc clean bytes lines seen=false contradiction=false reason='' remote_host
|
|
1057
|
+
backend=$(printf '%s' "$task" | jq -r '.backend // ""')
|
|
1058
|
+
target=$(printf '%s' "$task" | jq -r '.endpoint.target // ""')
|
|
1059
|
+
exists=$(printf '%s' "$task" | jq -r '.endpoint.exists // "unknown"')
|
|
1060
|
+
remote_host=$(printf '%s' "$task" | jq -r '.remote.host // ""')
|
|
1061
|
+
if [ -n "$remote_host" ]; then
|
|
1062
|
+
jq -n --arg observed "$SNAPSHOT_NOW" --arg reason "remote terminal evidence is not collected by the primary" \
|
|
1063
|
+
'{provenance:"remote-direct-report-terminal",trust:"untrusted-supplement",captured:false,observed_at:$observed,freshness:"not-collected",reason:$reason,lines:0,bytes:0,event_note_seen:false,contradiction:false}'
|
|
1064
|
+
return 0
|
|
1065
|
+
fi
|
|
1066
|
+
expected=$(printf '%s' "$task" | jq -r '"fm-" + (.id // "")')
|
|
1067
|
+
if [ -z "$target" ] || [ "$exists" = false ]; then
|
|
1068
|
+
[ "$exists" = false ] && reason="recorded endpoint is absent" || reason="no recorded endpoint"
|
|
1069
|
+
jq -n --arg observed "$SNAPSHOT_NOW" --arg reason "$reason" \
|
|
1070
|
+
'{provenance:"parent-direct-report-terminal",trust:"untrusted-supplement",captured:false,observed_at:$observed,freshness:"unknown",reason:$reason,lines:0,bytes:0,event_note_seen:false,contradiction:false}'
|
|
1071
|
+
return 0
|
|
1072
|
+
fi
|
|
1073
|
+
# shellcheck disable=SC2016 # Positional parameters expand inside the child bash, not here.
|
|
1074
|
+
out=$(fm_run_timed "$FM_SNAPSHOT_TERMINAL_TIMEOUT" bash -c \
|
|
1075
|
+
'. "$1"; fm_backend_capture "$2" "$3" "$4" "$5" | LC_ALL=C head -c "$6"; rc=${PIPESTATUS[0]}; [ "$rc" -eq 141 ] && rc=0; exit "$rc"' \
|
|
1076
|
+
fm-terminal-capture "$SCRIPT_DIR/fm-backend.sh" "$backend" "$target" "$FM_SNAPSHOT_TERMINAL_LINES" "$expected" "$FM_SNAPSHOT_TERMINAL_BYTES" 2>/dev/null)
|
|
1077
|
+
rc=$?
|
|
1078
|
+
if [ "$rc" -ne 0 ]; then
|
|
1079
|
+
[ "$rc" -eq 124 ] && reason="terminal capture timed out" || reason="terminal capture unavailable"
|
|
1080
|
+
jq -n --arg observed "$SNAPSHOT_NOW" --arg reason "$reason" \
|
|
1081
|
+
'{provenance:"parent-direct-report-terminal",trust:"untrusted-supplement",captured:false,observed_at:$observed,freshness:"unknown",reason:$reason,lines:0,bytes:0,event_note_seen:false,contradiction:false}'
|
|
1082
|
+
return 0
|
|
1083
|
+
fi
|
|
1084
|
+
clean=$(printf '%s' "$out" | tail -n "$FM_SNAPSHOT_TERMINAL_LINES" | LC_ALL=C head -c "$FM_SNAPSHOT_TERMINAL_BYTES")
|
|
1085
|
+
if command -v perl >/dev/null 2>&1; then
|
|
1086
|
+
clean=$(printf '%s' "$clean" | perl -pe 's/\e\[[0-?]*[ -\/]*[@-~]//g; s/[^\x09\x0A\x0D\x20-\x7E]//g')
|
|
1087
|
+
else
|
|
1088
|
+
clean=$(printf '%s' "$clean" | LC_ALL=C tr -cd '\11\12\15\40-\176')
|
|
1089
|
+
fi
|
|
1090
|
+
bytes=$(printf '%s' "$clean" | LC_ALL=C wc -c | tr -d ' ')
|
|
1091
|
+
if [ -n "$clean" ]; then
|
|
1092
|
+
lines=$(printf '%s\n' "$clean" | wc -l | tr -d ' ')
|
|
1093
|
+
else
|
|
1094
|
+
lines=0
|
|
1095
|
+
fi
|
|
1096
|
+
if [ -n "$note" ]; then
|
|
1097
|
+
case "$clean" in *"$note"*) seen=true ;; esac
|
|
1098
|
+
fi
|
|
1099
|
+
if [ "$seen" = true ] && [ "$evidence_contradicts" = true ]; then contradiction=true; fi
|
|
1100
|
+
jq -n \
|
|
1101
|
+
--arg observed "$SNAPSHOT_NOW" \
|
|
1102
|
+
--argjson lines "$lines" \
|
|
1103
|
+
--argjson bytes "$bytes" \
|
|
1104
|
+
--argjson seen "$seen" \
|
|
1105
|
+
--argjson contradiction "$contradiction" \
|
|
1106
|
+
'{provenance:"parent-direct-report-terminal",trust:"untrusted-supplement",captured:true,observed_at:$observed,freshness:"fresh",reason:null,lines:$lines,bytes:$bytes,event_note_seen:$seen,contradiction:$contradiction}'
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
parent_evidence_reconciliation_json() { # <summary-json> <activities-json> <decisions-json>
|
|
1110
|
+
jq -n --argjson summary "$1" --argjson activities "$2" --argjson decisions "$3" '
|
|
1111
|
+
def keyed: . != null and . != "" and . != "default";
|
|
1112
|
+
def result($e; $matches; $complete; $surface):
|
|
1113
|
+
$e + {
|
|
1114
|
+
verdict:(if ($e.key | keyed | not) then "inconclusive"
|
|
1115
|
+
elif ($matches | length) > 0 then "corroborates"
|
|
1116
|
+
elif $complete then "contradicts"
|
|
1117
|
+
else "inconclusive" end),
|
|
1118
|
+
compared_to:$surface,
|
|
1119
|
+
matched:(if ($e.key | keyed) then ($matches[0] // null) else null end)
|
|
1120
|
+
};
|
|
1121
|
+
([ $activities[] as $e
|
|
1122
|
+
| if $e.verb == "working" then
|
|
1123
|
+
([ $summary.active_children[]
|
|
1124
|
+
| select(if ($e.key | keyed) then .id == $e.key else true end)
|
|
1125
|
+
| {surface:"active_children",id,key:null,verb:"working"}]) as $matches
|
|
1126
|
+
| result($e; $matches;
|
|
1127
|
+
$summary.counts.active_children == ($summary.active_children | length);
|
|
1128
|
+
"active_children")
|
|
1129
|
+
elif $e.verb == "paused" then
|
|
1130
|
+
([ $summary.holds[]
|
|
1131
|
+
| select(if ($e.key | keyed) then .id == $e.key or .blocked_by == $e.key else true end)
|
|
1132
|
+
| {surface:"holds",id,key:(.blocked_by // null),verb:"paused"}]) as $matches
|
|
1133
|
+
| result($e; $matches;
|
|
1134
|
+
$summary.counts.holds == ($summary.holds | length);
|
|
1135
|
+
"holds")
|
|
1136
|
+
else
|
|
1137
|
+
$e + {verdict:"inconclusive",compared_to:null,matched:null}
|
|
1138
|
+
end ]) as $activity_results
|
|
1139
|
+
| ([ $decisions[] as $e
|
|
1140
|
+
| if $e.verb == "needs-decision" then
|
|
1141
|
+
([ $summary.decisions_open[]
|
|
1142
|
+
| select(.verb == "needs-decision")
|
|
1143
|
+
| select(if ($e.key | keyed) then .key == $e.key else true end)
|
|
1144
|
+
| {surface:"decisions_open",id,key,verb}]) as $matches
|
|
1145
|
+
| result($e; $matches;
|
|
1146
|
+
$summary.counts.decisions_open == ($summary.decisions_open | length);
|
|
1147
|
+
"decisions_open")
|
|
1148
|
+
elif $e.verb == "blocked" then
|
|
1149
|
+
([ $summary.decisions_open[]
|
|
1150
|
+
| select(.verb == "blocked")
|
|
1151
|
+
| select(if ($e.key | keyed) then .key == $e.key or .id == $e.key else true end)
|
|
1152
|
+
| {surface:"decisions_open",id,key,verb}]
|
|
1153
|
+
+ [ $summary.holds[]
|
|
1154
|
+
| select(if ($e.key | keyed) then .id == $e.key or .blocked_by == $e.key else true end)
|
|
1155
|
+
| {surface:"holds",id,key:(.blocked_by // null),verb:"blocked"}]) as $matches
|
|
1156
|
+
| result($e; $matches;
|
|
1157
|
+
($summary.counts.decisions_open == ($summary.decisions_open | length)
|
|
1158
|
+
and $summary.counts.holds == ($summary.holds | length));
|
|
1159
|
+
"decisions_open_or_holds")
|
|
1160
|
+
else
|
|
1161
|
+
$e + {verdict:"inconclusive",compared_to:null,matched:null}
|
|
1162
|
+
end ]) as $decision_results
|
|
1163
|
+
| {provenance:"parent-status-keyed-fold",trust:"untrusted-supplement",
|
|
1164
|
+
activities:$activity_results,decisions:$decision_results,
|
|
1165
|
+
contradiction:any(($activity_results + $decision_results)[]; .verdict == "contradicts"),
|
|
1166
|
+
inconclusive:any(($activity_results + $decision_results)[]; .verdict == "inconclusive")}'
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
secondmate_current_json() { # <parent-tasks-json>
|
|
1170
|
+
local tasks=$1 registry union rows total_registered total shown truncated
|
|
1171
|
+
local row id home host remote registered registry_error task sampled_spawn_gen status_file event_raw event_note event_epoch event_age
|
|
1172
|
+
local activity_scan activities decisions reconciliation provenance freshness reason summary summary_rc summary_bytes summary_sampled summary_valid summary_reason summary_invalidity state current_reason terminal terminal_contradiction contradiction
|
|
1173
|
+
local records='[]' seen_homes=''
|
|
1174
|
+
registry=$(registry_secondmates_json) || return 1
|
|
1175
|
+
union=$(jq -n --argjson registry "$registry" --argjson tasks "$tasks" '
|
|
1176
|
+
($registry.records // []) as $registered
|
|
1177
|
+
| (($registered | map(.id)) // []) as $registered_ids
|
|
1178
|
+
| ([ $registered[] as $r
|
|
1179
|
+
| $r + {parent_task:([$tasks[] | select(.id == $r.id)][0] // null)} ]
|
|
1180
|
+
+ [ $tasks[] | select(.kind == "secondmate") as $t
|
|
1181
|
+
| select(($registered_ids | index($t.id)) == null)
|
|
1182
|
+
| {id:$t.id,home:($t.paths.home.path // null),
|
|
1183
|
+
registered:(if $registry.complete == true then false else null end),
|
|
1184
|
+
registry_error:(if $registry.complete == true
|
|
1185
|
+
then "secondmate metadata is not registered"
|
|
1186
|
+
else "secondmate registration is unknown because the registry read is incomplete or unavailable" end),
|
|
1187
|
+
parent_task:$t} ])
|
|
1188
|
+
| sort_by(.id)
|
|
1189
|
+
| {registry:$registry,records:.}') || return 1
|
|
1190
|
+
total_registered=$(printf '%s' "$union" | jq '[.records[] | select(.registered)] | length')
|
|
1191
|
+
total=$(printf '%s' "$union" | jq '.records | length')
|
|
1192
|
+
rows=$(printf '%s' "$union" | jq -c --argjson cap "$FM_SNAPSHOT_SECONDMATES" '(if $cap == 0 then .records else .records[:$cap] end)[]')
|
|
1193
|
+
shown=$(printf '%s\n' "$rows" | grep -c . || true)
|
|
1194
|
+
truncated=$((total - shown))
|
|
1195
|
+
|
|
1196
|
+
while IFS= read -r row; do
|
|
1197
|
+
[ -n "$row" ] || continue
|
|
1198
|
+
id=$(printf '%s' "$row" | jq -r '.id')
|
|
1199
|
+
home=$(printf '%s' "$row" | jq -r '.home // ""')
|
|
1200
|
+
host=$(printf '%s' "$row" | jq -r '.host // ""')
|
|
1201
|
+
remote=$(printf '%s' "$row" | jq -r '.remote // false')
|
|
1202
|
+
registered=$(printf '%s' "$row" | jq -r '.registered')
|
|
1203
|
+
registry_error=$(printf '%s' "$row" | jq -r '.registry_error // ""')
|
|
1204
|
+
task=$(printf '%s' "$row" | jq -c '.parent_task // {}')
|
|
1205
|
+
sampled_spawn_gen=$(printf '%s' "$task" | jq -r '.spawn_gen // ""')
|
|
1206
|
+
status_file=$(printf '%s' "$task" | jq -r '.paths.status_log.path // ""')
|
|
1207
|
+
event_raw=$(printf '%s' "$task" | jq -r '.paths.status_log.last_event.raw // ""')
|
|
1208
|
+
event_note=$(printf '%s' "$task" | jq -r '.paths.status_log.last_event.note // ""')
|
|
1209
|
+
activity_scan=$(bounded_parent_activities_json "$status_file")
|
|
1210
|
+
activities=$(printf '%s' "$activity_scan" | jq -c '.records')
|
|
1211
|
+
decisions=$(printf '%s' "$task" | jq -c '.hints.open_decisions // []')
|
|
1212
|
+
event_epoch=$(file_mtime_epoch "$status_file")
|
|
1213
|
+
event_age=null
|
|
1214
|
+
if [ -n "$event_epoch" ]; then
|
|
1215
|
+
event_age=$((SNAPSHOT_EPOCH - event_epoch))
|
|
1216
|
+
[ "$event_age" -lt 0 ] && event_age=0
|
|
1217
|
+
fi
|
|
1218
|
+
|
|
1219
|
+
reason=$registry_error
|
|
1220
|
+
summary='{}'
|
|
1221
|
+
summary_sampled=false
|
|
1222
|
+
summary_valid=false
|
|
1223
|
+
if [ -z "$reason" ] && [ -z "$home" ]; then reason="no recorded secondmate home"; fi
|
|
1224
|
+
if [ -z "$reason" ]; then
|
|
1225
|
+
case "$home" in
|
|
1226
|
+
/*) : ;;
|
|
1227
|
+
*) reason="invalid home: registered path is not absolute" ;;
|
|
1228
|
+
esac
|
|
1229
|
+
fi
|
|
1230
|
+
if [ -z "$reason" ]; then
|
|
1231
|
+
if [ "$remote" = true ]; then
|
|
1232
|
+
[ -n "$host" ] || reason="invalid remote route: missing SSH host"
|
|
1233
|
+
case " $seen_homes " in
|
|
1234
|
+
*" $host:$home "*) reason="invalid home: duplicate resolved remote route" ;;
|
|
1235
|
+
*) seen_homes="$seen_homes $host:$home" ;;
|
|
1236
|
+
esac
|
|
1237
|
+
elif ! validate_secondmate_home "$id" "$home" 2>/dev/null; then
|
|
1238
|
+
reason="invalid home: $VALIDATION_ERROR"
|
|
1239
|
+
else
|
|
1240
|
+
home=$VALIDATED_HOME
|
|
1241
|
+
case " $seen_homes " in
|
|
1242
|
+
*" local:$home "*) reason="invalid home: duplicate resolved home route" ;;
|
|
1243
|
+
*) seen_homes="$seen_homes local:$home" ;;
|
|
1244
|
+
esac
|
|
1245
|
+
fi
|
|
1246
|
+
fi
|
|
1247
|
+
if [ -z "$reason" ]; then
|
|
1248
|
+
if [ "$remote" = true ]; then
|
|
1249
|
+
summary=$(fm_run_timed "$FM_SNAPSHOT_SECONDMATE_TIMEOUT" \
|
|
1250
|
+
"$SCRIPT_DIR/fm-on.sh" "$id" fm-fleet-snapshot.sh --secondmate-home-summary < /dev/null 2>/dev/null)
|
|
1251
|
+
summary_rc=$?
|
|
1252
|
+
else
|
|
1253
|
+
summary=$(fm_run_timed "$FM_SNAPSHOT_SECONDMATE_TIMEOUT" env \
|
|
1254
|
+
FM_ROOT_OVERRIDE="$FM_ROOT" \
|
|
1255
|
+
FM_HOME="$home" \
|
|
1256
|
+
FM_STATE_OVERRIDE="$home/state" \
|
|
1257
|
+
FM_DATA_OVERRIDE="$home/data" \
|
|
1258
|
+
FM_CONFIG_OVERRIDE="$home/config" \
|
|
1259
|
+
FM_PROJECTS_OVERRIDE="$home/projects" \
|
|
1260
|
+
FM_SNAPSHOT_NOW="$SNAPSHOT_NOW" \
|
|
1261
|
+
FM_SNAPSHOT_NOW_EPOCH="$SNAPSHOT_EPOCH" \
|
|
1262
|
+
FM_SNAPSHOT_SECONDMATE_CHILDREN="$FM_SNAPSHOT_SECONDMATE_CHILDREN" \
|
|
1263
|
+
FM_SNAPSHOT_SECONDMATE_QUEUED="$FM_SNAPSHOT_SECONDMATE_QUEUED" \
|
|
1264
|
+
FM_SNAPSHOT_SECONDMATE_DECISIONS="$FM_SNAPSHOT_SECONDMATE_DECISIONS" \
|
|
1265
|
+
FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME="$FM_SNAPSHOT_SECONDMATE_LANDED_PER_HOME" \
|
|
1266
|
+
"$SCRIPT_DIR/fm-fleet-snapshot.sh" --secondmate-home-summary 2>/dev/null)
|
|
1267
|
+
summary_rc=$?
|
|
1268
|
+
fi
|
|
1269
|
+
if [ "$summary_rc" -ne 0 ]; then
|
|
1270
|
+
summary='{}'
|
|
1271
|
+
[ "$summary_rc" -eq 124 ] && reason="structured home snapshot timed out" || reason="structured home snapshot failed"
|
|
1272
|
+
else
|
|
1273
|
+
summary_bytes=$(printf '%s' "$summary" | LC_ALL=C wc -c | tr -d ' ')
|
|
1274
|
+
if [ "$summary_bytes" -gt "$FM_SNAPSHOT_SECONDMATE_MAX_BYTES" ]; then
|
|
1275
|
+
reason="structured home snapshot exceeded byte limit"
|
|
1276
|
+
elif ! printf '%s' "$summary" | jq -e --arg home "$home" --arg generated "$SNAPSHOT_NOW" --argjson remote "$remote" '
|
|
1277
|
+
.schema == "fm-secondmate-home-summary.v1" and .home == $home
|
|
1278
|
+
and (($remote == true) or .generated == $generated)
|
|
1279
|
+
and (.valid | type) == "boolean" and (.state | type) == "string"
|
|
1280
|
+
and (.invalidity | type) == "object" and (.invalidity.ids | type) == "array"
|
|
1281
|
+
and (.active_children | type) == "array" and (.decisions_open | type) == "array"
|
|
1282
|
+
and (.holds | type) == "array" and (.queued | type) == "array"
|
|
1283
|
+
and (.landed | type) == "array" and (.endpoints | type) == "array"
|
|
1284
|
+
and (.counts | type) == "object" and (.omitted | type) == "array"
|
|
1285
|
+
' >/dev/null 2>&1; then
|
|
1286
|
+
reason="structured home snapshot was malformed or stale"
|
|
1287
|
+
else
|
|
1288
|
+
summary_sampled=true
|
|
1289
|
+
summary_valid=$(printf '%s' "$summary" | jq -r '.valid')
|
|
1290
|
+
if [ "$summary_valid" != true ]; then
|
|
1291
|
+
summary_reason=$(printf '%s' "$summary" | jq -r '.reason // "unknown reason"')
|
|
1292
|
+
summary_invalidity=$(printf '%s' "$summary" | jq -r '.invalidity.kind // "unknown"')
|
|
1293
|
+
case "$summary_invalidity" in
|
|
1294
|
+
child_current_unavailable|orphan_in_flight|unowned_current|terminal_in_flight) : ;;
|
|
1295
|
+
*) reason="structured home state invalid: $summary_reason" ;;
|
|
1296
|
+
esac
|
|
1297
|
+
fi
|
|
1298
|
+
fi
|
|
1299
|
+
fi
|
|
1300
|
+
fi
|
|
1301
|
+
|
|
1302
|
+
if [ -z "$reason" ]; then
|
|
1303
|
+
state=$(printf '%s' "$summary" | jq -r '.state')
|
|
1304
|
+
current_reason=
|
|
1305
|
+
if [ "$summary_valid" != true ]; then
|
|
1306
|
+
current_reason="structured home state invalid: $(printf '%s' "$summary" | jq -r '.reason // "unknown reason"')"
|
|
1307
|
+
fi
|
|
1308
|
+
reconciliation=$(parent_evidence_reconciliation_json "$summary" "$activities" "$decisions")
|
|
1309
|
+
contradiction=$(printf '%s' "$reconciliation" | jq -r '.contradiction')
|
|
1310
|
+
terminal_contradiction=$(printf '%s' "$reconciliation" | jq -r --arg note "$event_note" '
|
|
1311
|
+
any(.activities[]; .verdict == "contradicts" and .summary == $note)')
|
|
1312
|
+
if [ "$terminal_contradiction" = true ]; then
|
|
1313
|
+
terminal=$(terminal_evidence_json "$task" "$event_note" true)
|
|
1314
|
+
else
|
|
1315
|
+
terminal=$(jq -n --arg observed "$SNAPSHOT_NOW" \
|
|
1316
|
+
'{provenance:"parent-direct-report-terminal",trust:"untrusted-supplement",captured:false,observed_at:$observed,freshness:"not-collected",reason:"no useful contradiction check",lines:0,bytes:0,event_note_seen:false,contradiction:false}')
|
|
1317
|
+
fi
|
|
1318
|
+
if printf '%s' "$terminal" | jq -e '.contradiction == true' >/dev/null; then contradiction=true; fi
|
|
1319
|
+
record=$(jq -n \
|
|
1320
|
+
--arg id "$id" --arg home "$home" --arg host "$host" --argjson remote "$remote" --arg state "$state" --arg current_reason "$current_reason" --arg observed "$SNAPSHOT_NOW" \
|
|
1321
|
+
--arg spawn_gen "$sampled_spawn_gen" \
|
|
1322
|
+
--argjson registered "$registered" --argjson summary "$summary" --argjson summary_valid "$summary_valid" --argjson decisions "$decisions" \
|
|
1323
|
+
--argjson activities "$activities" --argjson activity_scan "$activity_scan" \
|
|
1324
|
+
--argjson reconciliation "$reconciliation" --argjson terminal "$terminal" --argjson contradiction "$contradiction" \
|
|
1325
|
+
--arg event_raw "$event_raw" --arg event_note "$event_note" --argjson event_age "$event_age" '
|
|
1326
|
+
{id:$id,home:$home,host:($host | if . == "" then null else . end),remote:$remote,registered:$registered,
|
|
1327
|
+
spawn_gen:($spawn_gen | if . == "" then null else . end),
|
|
1328
|
+
current:{state:$state,reason:($current_reason | if . == "" then null else . end)},invalidity:$summary.invalidity,
|
|
1329
|
+
reconcile_inventory:$summary.invalidity,
|
|
1330
|
+
provenance:{selected:"structured-home",structured_home:$home,summary_valid:$summary_valid,
|
|
1331
|
+
trust:(if $summary_valid then "complete" else "partial-structured" end),parent_event_role:"historical-only"},
|
|
1332
|
+
freshness:{status:"fresh",observed_at:$observed,age_seconds:0},
|
|
1333
|
+
active_children:$summary.active_children,
|
|
1334
|
+
decisions_open:$summary.decisions_open,holds:$summary.holds,queued:$summary.queued,
|
|
1335
|
+
landed:$summary.landed,endpoints:$summary.endpoints,counts:$summary.counts,omitted:$summary.omitted,
|
|
1336
|
+
parent_event:{raw:$event_raw,note:$event_note,age_seconds:$event_age,open_activities:$activities,open_decisions:$decisions,activity_scan:$activity_scan,reconciliation:$reconciliation},
|
|
1337
|
+
terminal_evidence:$terminal,contradiction:$contradiction}')
|
|
1338
|
+
else
|
|
1339
|
+
if [ -n "$event_raw" ]; then
|
|
1340
|
+
provenance='parent-event-fallback'
|
|
1341
|
+
freshness=historical-event
|
|
1342
|
+
else
|
|
1343
|
+
provenance=unknown
|
|
1344
|
+
freshness=unknown
|
|
1345
|
+
fi
|
|
1346
|
+
if [ -n "$event_raw" ]; then
|
|
1347
|
+
terminal=$(terminal_evidence_json "$task" "$event_note" false)
|
|
1348
|
+
else
|
|
1349
|
+
terminal=$(jq -n --arg observed "$SNAPSHOT_NOW" \
|
|
1350
|
+
'{provenance:"parent-direct-report-terminal",trust:"untrusted-supplement",captured:false,observed_at:$observed,freshness:"not-collected",reason:"no parent event to compare",lines:0,bytes:0,event_note_seen:false,contradiction:false}')
|
|
1351
|
+
fi
|
|
1352
|
+
record=$(jq -n \
|
|
1353
|
+
--arg id "$id" --arg home "$home" --arg host "$host" --argjson remote "$remote" --arg reason "$reason" --arg observed "$SNAPSHOT_NOW" \
|
|
1354
|
+
--arg spawn_gen "$sampled_spawn_gen" \
|
|
1355
|
+
--arg provenance "$provenance" --arg freshness "$freshness" --arg event_raw "$event_raw" --arg event_note "$event_note" \
|
|
1356
|
+
--argjson registered "$registered" --argjson event_age "$event_age" --argjson activities "$activities" --argjson activity_scan "$activity_scan" \
|
|
1357
|
+
--argjson decisions "$decisions" --argjson terminal "$terminal" --argjson summary "$summary" --argjson summary_sampled "$summary_sampled" '
|
|
1358
|
+
{id:$id,home:($home | if . == "" then null else . end),host:($host | if . == "" then null else . end),remote:$remote,registered:$registered,
|
|
1359
|
+
spawn_gen:($spawn_gen | if . == "" then null else . end),
|
|
1360
|
+
current:{state:"unknown",reason:$reason},invalidity:null,
|
|
1361
|
+
reconcile_inventory:(if $summary_sampled then $summary.invalidity else null end),
|
|
1362
|
+
provenance:{selected:$provenance,structured_home:($home | if . == "" then null else . end),parent_event_role:"fallback-only-not-current"},
|
|
1363
|
+
freshness:{status:$freshness,observed_at:$observed,age_seconds:$event_age},
|
|
1364
|
+
active_children:[],decisions_open:[],holds:[],queued:[],landed:[],endpoints:[],counts:{active_children:0,decisions_open:0,holds:0,queued:0,landed:0,endpoints:0},omitted:[],
|
|
1365
|
+
parent_event:{raw:$event_raw,note:$event_note,age_seconds:$event_age,open_activities:$activities,open_decisions:$decisions,activity_scan:$activity_scan},
|
|
1366
|
+
terminal_evidence:$terminal,contradiction:false}')
|
|
1367
|
+
fi
|
|
1368
|
+
records=$(jq -n --argjson records "$records" --argjson record "$record" '$records + [$record]')
|
|
1369
|
+
done <<EOF
|
|
1370
|
+
$rows
|
|
1371
|
+
EOF
|
|
1372
|
+
jq -n \
|
|
1373
|
+
--argjson registry "$(printf '%s' "$union" | jq '.registry')" \
|
|
1374
|
+
--argjson records "$records" \
|
|
1375
|
+
--argjson total_registered "$total_registered" \
|
|
1376
|
+
--argjson total "$total" \
|
|
1377
|
+
--argjson shown "$shown" \
|
|
1378
|
+
--argjson truncated "$truncated" \
|
|
1379
|
+
'{registry:$registry,records:$records,total_registered:$total_registered,total:$total,shown:$shown,truncated:$truncated}'
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
secondmate_landed_from_current_json() { # <secondmate-current-json>
|
|
1383
|
+
jq -n --argjson current "$1" '
|
|
1384
|
+
{records:[ $current.records[]
|
|
1385
|
+
| select(.provenance.selected == "structured-home") as $mate
|
|
1386
|
+
| $mate.landed[]
|
|
1387
|
+
| . + {home:$mate.home,home_id:$mate.id}],
|
|
1388
|
+
truncated:[ $current.records[]
|
|
1389
|
+
| select(.provenance.selected == "structured-home" and (.counts.landed > (.landed | length)))
|
|
1390
|
+
| .home],
|
|
1391
|
+
unreadable:[ $current.records[]
|
|
1392
|
+
| select(.current.state == "unknown" and .provenance.selected != "structured-home")
|
|
1393
|
+
| .home // ("<" + .id + ": unavailable>")],
|
|
1394
|
+
partial:[ $current.records[]
|
|
1395
|
+
| select(.provenance.selected == "structured-home" and .provenance.trust == "partial-structured")
|
|
1396
|
+
| .home // ("<" + .id + ": partial>")]}
|
|
1397
|
+
| .records |= sort_by([(.completion.date // ""), .id]) | .records |= reverse'
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
scout_report_lines() {
|
|
1401
|
+
local report id
|
|
1402
|
+
if [ ! -d "$DATA" ]; then
|
|
1403
|
+
jq -n '[]'
|
|
1404
|
+
return 0
|
|
1405
|
+
fi
|
|
1406
|
+
LC_ALL=C find "$DATA" -mindepth 2 -maxdepth 2 -type f -name report.md -print \
|
|
1407
|
+
| sort \
|
|
1408
|
+
| while IFS= read -r report; do
|
|
1409
|
+
id=$(basename "$(dirname "$report")")
|
|
1410
|
+
jq -n --arg id "$id" --arg path "$report" '{id:$id,path:$path}'
|
|
1411
|
+
done \
|
|
1412
|
+
| jq -s 'sort_by(.id)'
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
BACKLOG_JSON=$(backlog_json) || { echo "fm-fleet-snapshot: backlog read failed" >&2; exit 1; }
|
|
1416
|
+
TASKS_JSON=$(task_json_lines) || { echo "fm-fleet-snapshot: task snapshot failed" >&2; exit 1; }
|
|
1417
|
+
|
|
1418
|
+
if [ "$OUTPUT_MODE" = secondmate-home-summary ]; then
|
|
1419
|
+
secondmate_home_summary_json "$BACKLOG_JSON" "$TASKS_JSON" \
|
|
1420
|
+
|| { echo "fm-fleet-snapshot: secondmate home summary failed" >&2; exit 1; }
|
|
1421
|
+
exit 0
|
|
1422
|
+
fi
|
|
1423
|
+
|
|
1424
|
+
SCOUT_REPORTS_JSON=$(scout_report_lines)
|
|
1425
|
+
MAIN_INVENTORY_JSON=$(main_inventory_json "$BACKLOG_JSON" "$TASKS_JSON") \
|
|
1426
|
+
|| { echo "fm-fleet-snapshot: main inventory summary failed" >&2; exit 1; }
|
|
1427
|
+
SECONDMATE_CURRENT_JSON=$(secondmate_current_json "$TASKS_JSON") \
|
|
1428
|
+
|| { echo "fm-fleet-snapshot: registered secondmate aggregation failed" >&2; exit 1; }
|
|
1429
|
+
SECONDMATE_LANDED_JSON=$(secondmate_landed_from_current_json "$SECONDMATE_CURRENT_JSON") \
|
|
1430
|
+
|| { echo "fm-fleet-snapshot: secondmate landed projection failed" >&2; exit 1; }
|
|
1431
|
+
|
|
1432
|
+
jq -n \
|
|
1433
|
+
--arg generated "$SNAPSHOT_NOW" \
|
|
1434
|
+
--arg fm_home "$FM_HOME" \
|
|
1435
|
+
--arg fm_root "$FM_ROOT" \
|
|
1436
|
+
--arg state "$STATE" \
|
|
1437
|
+
--arg data "$DATA" \
|
|
1438
|
+
--arg config "$CONFIG" \
|
|
1439
|
+
--arg projects "$PROJECTS" \
|
|
1440
|
+
--argjson backlog "$BACKLOG_JSON" \
|
|
1441
|
+
--argjson tasks "$TASKS_JSON" \
|
|
1442
|
+
--argjson main_inventory "$MAIN_INVENTORY_JSON" \
|
|
1443
|
+
--argjson scout_reports "$SCOUT_REPORTS_JSON" \
|
|
1444
|
+
--argjson secondmate_current "$SECONDMATE_CURRENT_JSON" \
|
|
1445
|
+
--argjson secondmate_landed "$SECONDMATE_LANDED_JSON" \
|
|
1446
|
+
'def backlog_by_id($id): ($backlog.records[]? | select(.structured == true and .id == $id) | .) // null;
|
|
1447
|
+
def task_by_id($id): ($tasks[]? | select(.id == $id) | .) // null;
|
|
1448
|
+
def report_kind($id): (task_by_id($id).kind // backlog_by_id($id).kind // "scout");
|
|
1449
|
+
{
|
|
1450
|
+
schema:"fm-fleet-snapshot.v1",
|
|
1451
|
+
generated:$generated,
|
|
1452
|
+
fm_home:$fm_home,
|
|
1453
|
+
roots:{fm_root:$fm_root,state:$state,data:$data,config:$config,projects:$projects},
|
|
1454
|
+
backlog:$backlog,
|
|
1455
|
+
tasks:($tasks | map(. + {backlog:backlog_by_id(.id)})),
|
|
1456
|
+
main_inventory:$main_inventory,
|
|
1457
|
+
scout_reports:($scout_reports | map(. + {kind:report_kind(.id)})),
|
|
1458
|
+
secondmate_current:$secondmate_current,
|
|
1459
|
+
secondmate_landed:$secondmate_landed,
|
|
1460
|
+
secondmate_guidance:{
|
|
1461
|
+
note:"For kind=secondmate, bearings selects validated structured state from that registered home; parent events and bounded terminal evidence are fallback-only supplements and never current-state authority."
|
|
1462
|
+
}
|
|
1463
|
+
}'
|