@zerwiz/ymir 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +291 -0
- package/.agents/agents/bragi-marketer.md +111 -0
- package/.agents/agents/brokk.md +352 -0
- package/.agents/agents/forseti-reviewer.md +93 -0
- package/.agents/agents/frigg-consent.md +77 -0
- package/.agents/agents/groa-update.md +77 -0
- package/.agents/agents/hnoss-designer.md +69 -0
- package/.agents/agents/huginn-researcher.md +99 -0
- package/.agents/agents/jord-projects.md +78 -0
- package/.agents/agents/kvasir-scout.md +91 -0
- package/.agents/agents/mimir-planner.md +92 -0
- package/.agents/agents/muninn-stow.md +77 -0
- package/.agents/agents/saga-bearings.md +78 -0
- package/.agents/agents/sindri-developer.md +114 -0
- package/.agents/agents/snotra-documenter.md +105 -0
- package/.agents/agents/syn-recovery.md +78 -0
- package/.agents/agents/tyr-check.md +78 -0
- package/.agents/agents/urdh-hold.md +77 -0
- package/.agents/agents/volundr-smith.md +80 -0
- package/.agents/agents/vor-diagnostics.md +77 -0
- package/.agents/assets/agents/naming.md +50 -0
- package/.agents/assets/agents/registry.md +93 -0
- package/.agents/assets/agents/runtime.md +36 -0
- package/.agents/assets/agents/toon-tasks-cli.md +42 -0
- package/.agents/assets/templates/company_entity.template.md +37 -0
- package/.agents/backend/backends/cmux.sh +649 -0
- package/.agents/backend/backends/herdr-eventwait.py +157 -0
- package/.agents/backend/backends/herdr-workspace-move.py +114 -0
- package/.agents/backend/backends/herdr.sh +3386 -0
- package/.agents/backend/backends/orca.sh +290 -0
- package/.agents/backend/backends/tmux.sh +357 -0
- package/.agents/backend/backends/zellij.sh +700 -0
- package/.agents/backend/fm-afk-launch.sh +654 -0
- package/.agents/backend/fm-afk-return.sh +241 -0
- package/.agents/backend/fm-afk-start.sh +172 -0
- package/.agents/backend/fm-arm-command-policy.mjs +960 -0
- package/.agents/backend/fm-arm-pretool-check.sh +200 -0
- package/.agents/backend/fm-backend-hometag-lib.sh +52 -0
- package/.agents/backend/fm-backend.sh +981 -0
- package/.agents/backend/fm-backlog-handoff.sh +909 -0
- package/.agents/backend/fm-backlog-receive.sh +187 -0
- package/.agents/backend/fm-backlog-transition-lib.sh +779 -0
- package/.agents/backend/fm-bearings-board.sh +204 -0
- package/.agents/backend/fm-bearings-snapshot.sh +546 -0
- package/.agents/backend/fm-bootstrap.sh +1568 -0
- package/.agents/backend/fm-branch-outcome.sh +203 -0
- package/.agents/backend/fm-branch-prompt.sh +101 -0
- package/.agents/backend/fm-brief.sh +452 -0
- package/.agents/backend/fm-busy-event.sh +234 -0
- package/.agents/backend/fm-busy-lib.sh +986 -0
- package/.agents/backend/fm-captain-hold.sh +1001 -0
- package/.agents/backend/fm-cd-command-policy.mjs +153 -0
- package/.agents/backend/fm-cd-pretool-check.sh +190 -0
- package/.agents/backend/fm-check-lib.sh +74 -0
- package/.agents/backend/fm-check-register.sh +42 -0
- package/.agents/backend/fm-check-unregister.sh +52 -0
- package/.agents/backend/fm-classify-lib.sh +1770 -0
- package/.agents/backend/fm-claude-stop-autoarm.sh +334 -0
- package/.agents/backend/fm-composer-lib.sh +1416 -0
- package/.agents/backend/fm-config-inherit-lib.sh +1202 -0
- package/.agents/backend/fm-config-push.sh +254 -0
- package/.agents/backend/fm-control-lib.sh +251 -0
- package/.agents/backend/fm-control.sh +878 -0
- package/.agents/backend/fm-crew-state.sh +625 -0
- package/.agents/backend/fm-cursor-lib.sh +243 -0
- package/.agents/backend/fm-decision-hold.sh +232 -0
- package/.agents/backend/fm-doc-audience-check.sh +269 -0
- package/.agents/backend/fm-dod-lib.sh +67 -0
- package/.agents/backend/fm-ensure-agents-md.sh +253 -0
- package/.agents/backend/fm-extension-launch-barrier.mjs +129 -0
- package/.agents/backend/fm-extension.mjs +2577 -0
- package/.agents/backend/fm-extension.sh +16 -0
- package/.agents/backend/fm-ff-lib.sh +420 -0
- package/.agents/backend/fm-fleet-snapshot.sh +1463 -0
- package/.agents/backend/fm-fleet-sync.sh +458 -0
- package/.agents/backend/fm-fleet-view.sh +96 -0
- package/.agents/backend/fm-gate-refuse-lib.sh +102 -0
- package/.agents/backend/fm-guard.sh +242 -0
- package/.agents/backend/fm-harness.sh +195 -0
- package/.agents/backend/fm-herdr-ci-cleanup.sh +115 -0
- package/.agents/backend/fm-herdr-lab.sh +346 -0
- package/.agents/backend/fm-herdr-session-cleanup.sh +337 -0
- package/.agents/backend/fm-home-seed.sh +984 -0
- package/.agents/backend/fm-home-summary-refresh.sh +255 -0
- package/.agents/backend/fm-hook-host-lib.sh +36 -0
- package/.agents/backend/fm-inactive-reconcile.sh +531 -0
- package/.agents/backend/fm-inbox.sh +399 -0
- package/.agents/backend/fm-install-actionlint.sh +84 -0
- package/.agents/backend/fm-install-herdr.sh +96 -0
- package/.agents/backend/fm-install-shellcheck.sh +84 -0
- package/.agents/backend/fm-install-treehouse.sh +95 -0
- package/.agents/backend/fm-kimi-turnend-hook.sh +276 -0
- package/.agents/backend/fm-lease-lib.sh +218 -0
- package/.agents/backend/fm-lease.sh +189 -0
- package/.agents/backend/fm-line-cap-lib.sh +51 -0
- package/.agents/backend/fm-lint-workflows.sh +137 -0
- package/.agents/backend/fm-lint.sh +591 -0
- package/.agents/backend/fm-lock-lib.sh +104 -0
- package/.agents/backend/fm-lock.sh +107 -0
- package/.agents/backend/fm-marker-lib.sh +12 -0
- package/.agents/backend/fm-merge-local.sh +74 -0
- package/.agents/backend/fm-merge-outcome-lib.sh +138 -0
- package/.agents/backend/fm-nm-run-lib.sh +124 -0
- package/.agents/backend/fm-on.sh +125 -0
- package/.agents/backend/fm-operational-input.sh +253 -0
- package/.agents/backend/fm-peek.sh +46 -0
- package/.agents/backend/fm-pending-reply-lib.sh +1409 -0
- package/.agents/backend/fm-pr-check.sh +135 -0
- package/.agents/backend/fm-pr-lib.sh +1016 -0
- package/.agents/backend/fm-pr-merge.sh +707 -0
- package/.agents/backend/fm-pr-poll.sh +110 -0
- package/.agents/backend/fm-primary-scope-lib.sh +33 -0
- package/.agents/backend/fm-procevent-extension-capture.pl +259 -0
- package/.agents/backend/fm-procevent-lavish.sh +628 -0
- package/.agents/backend/fm-procevent-lib.sh +892 -0
- package/.agents/backend/fm-procevent-quota.sh +290 -0
- package/.agents/backend/fm-procevent-remote-reply.sh +577 -0
- package/.agents/backend/fm-procevent-when.sh +504 -0
- package/.agents/backend/fm-procevent.sh +1466 -0
- package/.agents/backend/fm-project-mode.sh +92 -0
- package/.agents/backend/fm-project-origin-lib.sh +180 -0
- package/.agents/backend/fm-promote.sh +282 -0
- package/.agents/backend/fm-public-followup-emit.sh +260 -0
- package/.agents/backend/fm-public-followup-lib.sh +398 -0
- package/.agents/backend/fm-public-followup.sh +1330 -0
- package/.agents/backend/fm-push-transition-lib.sh +168 -0
- package/.agents/backend/fm-quota-axi-lib.sh +93 -0
- package/.agents/backend/fm-quota-choose.sh +384 -0
- package/.agents/backend/fm-remote-delta-read.sh +189 -0
- package/.agents/backend/fm-remote-doctor.sh +800 -0
- package/.agents/backend/fm-remote-entrypoint.sh +193 -0
- package/.agents/backend/fm-remote-file.sh +234 -0
- package/.agents/backend/fm-remote-home-provision.sh +259 -0
- package/.agents/backend/fm-remote-home-seed.sh +259 -0
- package/.agents/backend/fm-remote-inherit-push.sh +90 -0
- package/.agents/backend/fm-remote-inherit.sh +179 -0
- package/.agents/backend/fm-remote-job-lib.sh +1241 -0
- package/.agents/backend/fm-remote-job-reap-orphans.sh +137 -0
- package/.agents/backend/fm-remote-job-worker.sh +1124 -0
- package/.agents/backend/fm-remote-readiness-lib.sh +44 -0
- package/.agents/backend/fm-remote-secondmate-control.sh +340 -0
- package/.agents/backend/fm-review-diff.sh +158 -0
- package/.agents/backend/fm-secondmate-charter-lib.sh +44 -0
- package/.agents/backend/fm-secondmate-nudge-lib.sh +68 -0
- package/.agents/backend/fm-secondmate-parent-lib.sh +70 -0
- package/.agents/backend/fm-secondmate-reconcile.sh +371 -0
- package/.agents/backend/fm-secondmate-registry-lib.sh +311 -0
- package/.agents/backend/fm-secondmate-report.sh +88 -0
- package/.agents/backend/fm-send.sh +1059 -0
- package/.agents/backend/fm-session-lock-lib.sh +176 -0
- package/.agents/backend/fm-session-start.sh +970 -0
- package/.agents/backend/fm-sessionstart-cursor.sh +40 -0
- package/.agents/backend/fm-sessionstart-nudge.sh +45 -0
- package/.agents/backend/fm-sessionstart-run.sh +145 -0
- package/.agents/backend/fm-spawn.sh +3153 -0
- package/.agents/backend/fm-startup-memory-budget-lib.sh +224 -0
- package/.agents/backend/fm-startup-memory-budget.sh +94 -0
- package/.agents/backend/fm-startup-network.sh +642 -0
- package/.agents/backend/fm-stow-cascade.sh +251 -0
- package/.agents/backend/fm-subagent-pretool-check.sh +207 -0
- package/.agents/backend/fm-supervise-daemon.sh +1732 -0
- package/.agents/backend/fm-supervision-instructions.sh +215 -0
- package/.agents/backend/fm-supervision-lib.sh +90 -0
- package/.agents/backend/fm-supervisor-target-lib.sh +78 -0
- package/.agents/backend/fm-tangle-lib.sh +53 -0
- package/.agents/backend/fm-task-inbox-lib.sh +402 -0
- package/.agents/backend/fm-tasks-axi-lib.sh +123 -0
- package/.agents/backend/fm-teardown.sh +2922 -0
- package/.agents/backend/fm-test-isolation-proof.sh +588 -0
- package/.agents/backend/fm-test-run.sh +2191 -0
- package/.agents/backend/fm-timeout-lib.sh +141 -0
- package/.agents/backend/fm-timing-lib.sh +179 -0
- package/.agents/backend/fm-tmux-lib.sh +291 -0
- package/.agents/backend/fm-tool-update-check.sh +898 -0
- package/.agents/backend/fm-trace-context-lib.sh +227 -0
- package/.agents/backend/fm-transition-lib.sh +103 -0
- package/.agents/backend/fm-turnend-guard-cursor.sh +391 -0
- package/.agents/backend/fm-turnend-guard-grok.sh +90 -0
- package/.agents/backend/fm-turnend-guard.sh +432 -0
- package/.agents/backend/fm-update.sh +110 -0
- package/.agents/backend/fm-vendor-auth-probe.sh +190 -0
- package/.agents/backend/fm-voice-client.py +1373 -0
- package/.agents/backend/fm-voice-relay.py +1256 -0
- package/.agents/backend/fm-wake-drain.sh +592 -0
- package/.agents/backend/fm-wake-grant.sh +127 -0
- package/.agents/backend/fm-wake-lib.sh +1849 -0
- package/.agents/backend/fm-watch-arm.sh +602 -0
- package/.agents/backend/fm-watch-checkpoint.sh +109 -0
- package/.agents/backend/fm-watch.sh +1962 -0
- package/.agents/backend/fm-x-dismiss.sh +116 -0
- package/.agents/backend/fm-x-followup.sh +286 -0
- package/.agents/backend/fm-x-lib.sh +1000 -0
- package/.agents/backend/fm-x-link.sh +234 -0
- package/.agents/backend/fm-x-poll.sh +185 -0
- package/.agents/backend/fm-x-reply.sh +385 -0
- package/.agents/backend/fm_voice_frame.py +166 -0
- package/.agents/backend/fm_voice_records.py +574 -0
- package/.agents/backend/model-bridge.py +290 -0
- package/.agents/backend/opencode-go-bridge.py +30 -0
- package/.agents/bus/README.md +12 -0
- package/.agents/bus/messages.json +1 -0
- package/.agents/config/agents.machine.example.yaml +16 -0
- package/.agents/config/agents.yaml +50 -0
- package/.agents/config/agents.yaml.example +72 -0
- package/.agents/config/crew-dispatch.json +22 -0
- package/.agents/config/cron.yaml +9 -0
- package/.agents/config/eindri-dispatch.json +67 -0
- package/.agents/config/eindri-harness +1 -0
- package/.agents/config/startup-memory-budget +1 -0
- package/.agents/config/tailscale-sync.yaml +27 -0
- package/.agents/filebrowser/.filebrowser.json +6 -0
- package/.agents/filebrowser/README.md +11 -0
- package/.agents/gateway/README.md +12 -0
- package/.agents/github/README.md +15 -0
- package/.agents/github/webhooks/.gitkeep +0 -0
- package/.agents/github/workflows/.gitkeep +0 -0
- package/.agents/harness/opencode/plugins/lib/rodd-operational-input.js +37 -0
- package/.agents/harness/opencode/plugins/package.json +4 -0
- package/.agents/harness/opencode/plugins/saga-sessionstart.js +77 -0
- package/.agents/harness/opencode/plugins/syn-cd-check.js +61 -0
- package/.agents/harness/opencode/plugins/syn-pretool-check.js +68 -0
- package/.agents/harness/opencode/plugins/syn-turnend-guard.js +105 -0
- package/.agents/harness/opencode/plugins/syn-watch-arm.js +521 -0
- package/.agents/memory/README.md +46 -0
- package/.agents/memory/kaia.engram +0 -0
- package/.agents/memory/kaia.engram-shm +0 -0
- package/.agents/memory/kaia.engram-wal +0 -0
- package/.agents/memory/runes_audit.md +5 -0
- package/.agents/memory/well/episodes.jsonl +367 -0
- package/.agents/memory/well/workspace.jsonl +27 -0
- package/.agents/migrations/0001-hodd-layout.sh +38 -0
- package/.agents/migrations/0002-a2a-mcp.sh +27 -0
- package/.agents/migrations/0003-private-data-separation.sh +111 -0
- package/.agents/migrations/0004-hoard-and-realms.sh +104 -0
- package/.agents/sandbox/Dockerfile.utgard +19 -0
- package/.agents/sandbox/README.md +14 -0
- package/.agents/sandbox/utgard.config.json +14 -0
- package/.agents/skills/README.md +59 -0
- package/.agents/skills/bragi-marketing/README.md +3 -0
- package/.agents/skills/bragi-marketing/SKILL.md +82 -0
- package/.agents/skills/eindri-homes/README.md +3 -0
- package/.agents/skills/eindri-homes/SKILL.md +291 -0
- package/.agents/skills/eindri-homes/assets/control-plane.md +122 -0
- package/.agents/skills/eindri-homes/assets/dispatch.md +80 -0
- package/.agents/skills/frigg-consent/README.md +3 -0
- package/.agents/skills/frigg-consent/SKILL.md +56 -0
- package/.agents/skills/galdr-ymirsystem/README.md +3 -0
- package/.agents/skills/galdr-ymirsystem/SKILL.md +158 -0
- package/.agents/skills/galdr-ymirsystem/assets/README.md +74 -0
- package/.agents/skills/galdr-ymirsystem/assets/brokk-distro-runtime.md +390 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-method.md +113 -0
- package/.agents/skills/galdr-ymirsystem/assets/build-tool-categories.md +178 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-orchestration.md +518 -0
- package/.agents/skills/galdr-ymirsystem/assets/eindri-profiles.md +136 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/README.md +673 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/claude-code.md +197 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/codex.md +191 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/cursor.md +193 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/opencode.md +262 -0
- package/.agents/skills/galdr-ymirsystem/assets/harness-integration/pi.md +295 -0
- package/.agents/skills/galdr-ymirsystem/assets/hlidskjalf-ui.md +496 -0
- package/.agents/skills/galdr-ymirsystem/assets/installation.md +571 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models/HOST-DATA.example.md +72 -0
- package/.agents/skills/galdr-ymirsystem/assets/local-models.md +274 -0
- package/.agents/skills/galdr-ymirsystem/assets/memory-well.md +161 -0
- package/.agents/skills/galdr-ymirsystem/assets/nornir-jobs.md +377 -0
- package/.agents/skills/galdr-ymirsystem/assets/norse-naming.md +325 -0
- package/.agents/skills/galdr-ymirsystem/assets/odrerir-hall.md +129 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/einherjar-spawn.schema.json +81 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/herdr-profile.toml +75 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/pi-profile.yml +96 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot/supervision-tree.yml +163 -0
- package/.agents/skills/galdr-ymirsystem/assets/pi-boot-guide.md +58 -0
- package/.agents/skills/galdr-ymirsystem/assets/porting-upstream-to-norse.md +301 -0
- package/.agents/skills/galdr-ymirsystem/assets/principles.md +385 -0
- package/.agents/skills/galdr-ymirsystem/assets/reference-adoption.md +99 -0
- package/.agents/skills/galdr-ymirsystem/assets/registry.md +379 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-compliance.md +463 -0
- package/.agents/skills/galdr-ymirsystem/assets/runtime-components.md +553 -0
- package/.agents/skills/galdr-ymirsystem/assets/smidja.md +175 -0
- package/.agents/skills/galdr-ymirsystem/schemas/toon-schemas.md +190 -0
- package/.agents/skills/galdr-ymirsystem/scripts/README.md +45 -0
- package/.agents/skills/galdr-ymirsystem/scripts/bench-one.sh +248 -0
- package/.agents/skills/galdr-ymirsystem/scripts/compliance-check.sh +360 -0
- package/.agents/skills/galdr-ymirsystem/scripts/gpu-sample.sh +43 -0
- package/.agents/skills/galdr-ymirsystem/scripts/stop-all.sh +34 -0
- package/.agents/skills/galdr-ymirsystem/scripts/toon-check.py +188 -0
- package/.agents/skills/gjallarhorn-relay/README.md +3 -0
- package/.agents/skills/gjallarhorn-relay/SKILL.md +283 -0
- package/.agents/skills/groa-update/SKILL.md +70 -0
- package/.agents/skills/hamr-adapters/README.md +3 -0
- package/.agents/skills/hamr-adapters/SKILL.md +123 -0
- package/.agents/skills/hamr-adapters/references/common/control-and-recovery.md +37 -0
- package/.agents/skills/hamr-adapters/references/common/dispatch.md +32 -0
- package/.agents/skills/hamr-adapters/references/common/model-and-effort.md +42 -0
- package/.agents/skills/hamr-adapters/references/common/primary-hooks.md +40 -0
- package/.agents/skills/hamr-adapters/references/harness/claude.md +55 -0
- package/.agents/skills/hamr-adapters/references/harness/codex.md +43 -0
- package/.agents/skills/hamr-adapters/references/harness/cursor.md +75 -0
- package/.agents/skills/hamr-adapters/references/harness/grok.md +69 -0
- package/.agents/skills/hamr-adapters/references/harness/kimi.md +51 -0
- package/.agents/skills/hamr-adapters/references/harness/muse.md +70 -0
- package/.agents/skills/hamr-adapters/references/harness/opencode.md +42 -0
- package/.agents/skills/hamr-adapters/references/harness/pi.md +56 -0
- package/.agents/skills/herdr-panes/README.md +3 -0
- package/.agents/skills/herdr-panes/SKILL.md +92 -0
- package/.agents/skills/herdr-panes/assets/herdr-backend.md +346 -0
- package/.agents/skills/herdr-panes/assets/tmux-backend.md +115 -0
- package/.agents/skills/hnoss-design/DESIGN.md +19 -0
- package/.agents/skills/hnoss-design/README.md +3 -0
- package/.agents/skills/hnoss-design/SKILL.md +83 -0
- package/.agents/skills/hvild-afk/README.md +3 -0
- package/.agents/skills/hvild-afk/SKILL.md +241 -0
- package/.agents/skills/jord-projects/README.md +3 -0
- package/.agents/skills/jord-projects/SKILL.md +92 -0
- package/.agents/skills/lifecycle/SKILL.md +56 -0
- package/.agents/skills/lifecycle/smoke_test.sh +52 -0
- package/.agents/skills/lifecycle/start.sh +21 -0
- package/.agents/skills/lifecycle/status.sh +35 -0
- package/.agents/skills/lifecycle/stop.sh +16 -0
- package/.agents/skills/muninn-stow/README.md +3 -0
- package/.agents/skills/muninn-stow/SKILL.md +309 -0
- package/.agents/skills/no-mistakes/SKILL.md +379 -0
- package/.agents/skills/nornir-schedule/README.md +3 -0
- package/.agents/skills/nornir-schedule/SKILL.md +25 -0
- package/.agents/skills/nornir-schedule/assets/events.md +137 -0
- package/.agents/skills/nornir-schedule/assets/quota.md +131 -0
- package/.agents/skills/nsr-compliance/README.md +3 -0
- package/.agents/skills/nsr-compliance/SKILL.md +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/README.md +40 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/git-workflow.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/security.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/RULES/tech-stack.md +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/NSRcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/README.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/README.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/features/SKILL-template.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/SKILL.md +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/create_branch.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/safe_commit.sh +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/git_ops/sync_upstream.sh +6 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/README.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/SKILL.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/smoke_test.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/start.sh +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/status.sh +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/agents_skills/lifecycle/stop.sh +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/config/core_four.yaml +13 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/compliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/README.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/api.md +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/compliance-wiring.md +155 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/BEST_PRACTICES/frontend.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/README.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/clients/template-README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/deploy.sh +49 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/development.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/production.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/envs/staging.env.example +17 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/CI_CD/deployment/tenants/template.env.example +8 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/README.md +26 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/agent.md +7 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/developers/dev-1.md +20 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/linux.md +9 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/macos.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/shared.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/DEVELOPER_SETUP/windows.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/README.md +25 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/_template.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/HOSTING/hosting-1.md +41 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/README.md +21 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/README.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/deployment.md +19 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/disaster-recovery.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/RUNBOOK/ops-tasks.md +14 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/features/README.md +16 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/project-skills.md +146 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/research/software-compliance-research.md +269 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/docs/working-with-agents.md +260 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/AGENTS.md +38 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/ARCHITECTURE.md +36 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/BEST_PRACTICES.md +27 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/CI_CD.md +23 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/FEATURES.md +15 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/README.md +22 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/RULES.md +37 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/STRUCTURE.md +45 -0
- package/.agents/skills/nsr-compliance/assets/nsr/assets/root/TECH_STACK.md +28 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scaffold-spec.md +63 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/scaffold.sh +103 -0
- package/.agents/skills/nsr-compliance/assets/nsr/scripts/validate.sh +52 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/SKILL.md +71 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/config/core_four.yaml +16 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_danger.sh +102 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/check_wiring.sh +69 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/validate_code.sh +29 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/gates/verify_docs.py +66 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/generate.sh +109 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/result_envelope.json +14 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/envelopes/task_envelope.json +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/harness/runner.py +303 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/telemetry/logger.py +112 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/README.md +18 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/config/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/README.md +12 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_env.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_paths.sh +20 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/gates/check_platform.sh +23 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/README.md +11 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/harness/envelopes/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/README.md +10 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/installer/install.sh +125 -0
- package/.agents/skills/nsr-compliance/assets/nsrcompliance/templates/compliance/telemetry/README.md +11 -0
- package/.agents/skills/pr-ops/README.md +3 -0
- package/.agents/skills/pr-ops/SKILL.md +109 -0
- package/.agents/skills/ratatoskr-a2a/README.md +3 -0
- package/.agents/skills/ratatoskr-a2a/SKILL.md +148 -0
- package/.agents/skills/rules-check-drift/SKILL.md +81 -0
- package/.agents/skills/saga-bearings/README.md +3 -0
- package/.agents/skills/saga-bearings/SKILL.md +26 -0
- package/.agents/skills/saga-bearings/assets/bearings.md +161 -0
- package/.agents/skills/saga-bearings/assets/board-template.html +731 -0
- package/.agents/skills/saga-bearings/assets/recap.md +56 -0
- package/.agents/skills/syn-recovery/README.md +3 -0
- package/.agents/skills/syn-recovery/SKILL.md +54 -0
- package/.agents/skills/tyr-check/README.md +3 -0
- package/.agents/skills/tyr-check/SKILL.md +218 -0
- package/.agents/skills/urdh-hold/README.md +3 -0
- package/.agents/skills/urdh-hold/SKILL.md +25 -0
- package/.agents/skills/urdh-hold/assets/decisions.md +15 -0
- package/.agents/skills/urdh-hold/assets/hold.md +54 -0
- package/.agents/skills/vor-diagnostics/README.md +3 -0
- package/.agents/skills/vor-diagnostics/SKILL.md +68 -0
- package/.agents/skills/ymir-host/README.md +3 -0
- package/.agents/skills/ymir-host/SKILL.md +34 -0
- package/.agents/skills/ymir-host/assets/desktop.md +36 -0
- package/.agents/skills/ymir-host/assets/omarchy.md +297 -0
- package/.agents/skills/ymir-host/assets/runbooks.md +15 -0
- package/.agents/skills/ymir-host/assets/thjazi.md +220 -0
- package/.agents/skills/ymir-host/assets/update.md +64 -0
- package/.agents/tests/assets/board-render-harness.mjs +117 -0
- package/.agents/tests/cmux-test-safety.sh +51 -0
- package/.agents/tests/fixtures.sh +291 -0
- package/.agents/tests/fm-afk-inject-e2e.test.sh +428 -0
- package/.agents/tests/fm-afk-inject-herdr-e2e.test.sh +536 -0
- package/.agents/tests/fm-afk-launch.test.sh +961 -0
- package/.agents/tests/fm-afk-pi-herdr-return-e2e.test.sh +297 -0
- package/.agents/tests/fm-afk-return.test.sh +283 -0
- package/.agents/tests/fm-arm-pretool-check.test.sh +482 -0
- package/.agents/tests/fm-ask-user-authority.test.sh +42 -0
- package/.agents/tests/fm-backend-autodetect-smoke.test.sh +176 -0
- package/.agents/tests/fm-backend-cmux-smoke.test.sh +188 -0
- package/.agents/tests/fm-backend-cmux.test.sh +1166 -0
- package/.agents/tests/fm-backend-herdr-eventwait-smoke.test.sh +136 -0
- package/.agents/tests/fm-backend-herdr-eventwait.test.py +106 -0
- package/.agents/tests/fm-backend-herdr-focus-flash-e2e.test.sh +409 -0
- package/.agents/tests/fm-backend-herdr-launcher-workspace-e2e.test.sh +437 -0
- package/.agents/tests/fm-backend-herdr-presentation-e2e.test.sh +1424 -0
- package/.agents/tests/fm-backend-herdr-prune-safety-e2e.test.sh +180 -0
- package/.agents/tests/fm-backend-herdr-respawn-idem-e2e.test.sh +182 -0
- package/.agents/tests/fm-backend-herdr-smoke.test.sh +355 -0
- package/.agents/tests/fm-backend-herdr-workspace-per-home-e2e.test.sh +251 -0
- package/.agents/tests/fm-backend-herdr.test.sh +4606 -0
- package/.agents/tests/fm-backend-orca.test.sh +1357 -0
- package/.agents/tests/fm-backend-tmux-smoke.test.sh +173 -0
- package/.agents/tests/fm-backend-zellij-smoke.test.sh +205 -0
- package/.agents/tests/fm-backend-zellij.test.sh +1360 -0
- package/.agents/tests/fm-backend.test.sh +1142 -0
- package/.agents/tests/fm-backlog-atomicity.test.sh +2302 -0
- package/.agents/tests/fm-backlog-handoff.test.sh +1360 -0
- package/.agents/tests/fm-bearings-board-render.test.sh +135 -0
- package/.agents/tests/fm-bearings-board.test.sh +414 -0
- package/.agents/tests/fm-bearings-snapshot.test.sh +1990 -0
- package/.agents/tests/fm-bootstrap-network-parallel.test.sh +316 -0
- package/.agents/tests/fm-bootstrap.test.sh +1178 -0
- package/.agents/tests/fm-branch-supervision.test.sh +552 -0
- package/.agents/tests/fm-brief.test.sh +785 -0
- package/.agents/tests/fm-busy-adapter-wiring.test.sh +324 -0
- package/.agents/tests/fm-busy-state.test.sh +465 -0
- package/.agents/tests/fm-calm-pi-extension.test.sh +3979 -0
- package/.agents/tests/fm-captain-hold-lifecycle.test.sh +1187 -0
- package/.agents/tests/fm-cd-pretool-check.test.sh +400 -0
- package/.agents/tests/fm-check-unregister.test.sh +198 -0
- package/.agents/tests/fm-classify-corr-token.test.sh +543 -0
- package/.agents/tests/fm-classify-decision-key.test.sh +340 -0
- package/.agents/tests/fm-claude-stop-autoarm-live-e2e.test.sh +164 -0
- package/.agents/tests/fm-claude-stop-autoarm.test.sh +1190 -0
- package/.agents/tests/fm-cmux-claude-composer-live-e2e.test.sh +110 -0
- package/.agents/tests/fm-codex-continuity-live-e2e.test.sh +55 -0
- package/.agents/tests/fm-composer-ghost.test.sh +716 -0
- package/.agents/tests/fm-composer-lib.test.sh +666 -0
- package/.agents/tests/fm-composer-matrix-live-e2e.test.sh +220 -0
- package/.agents/tests/fm-control-herdr-smoke.test.sh +149 -0
- package/.agents/tests/fm-control-relaunch.test.sh +1530 -0
- package/.agents/tests/fm-control.test.sh +910 -0
- package/.agents/tests/fm-crew-state.test.sh +1610 -0
- package/.agents/tests/fm-cursor-harness.test.sh +404 -0
- package/.agents/tests/fm-cursor-primary-live-e2e.test.sh +217 -0
- package/.agents/tests/fm-cursor-primary.test.sh +708 -0
- package/.agents/tests/fm-daemon.test.sh +2740 -0
- package/.agents/tests/fm-documentation-audiences.test.sh +141 -0
- package/.agents/tests/fm-ensure-agents-md.test.sh +371 -0
- package/.agents/tests/fm-extension-binding.test.sh +2187 -0
- package/.agents/tests/fm-fleet-snapshot-view.test.sh +816 -0
- package/.agents/tests/fm-fleet-sync.test.sh +721 -0
- package/.agents/tests/fm-gate-refuse.test.sh +356 -0
- package/.agents/tests/fm-gitignore-config.test.sh +89 -0
- package/.agents/tests/fm-gotmp.test.sh +230 -0
- package/.agents/tests/fm-grok-continuity-live-e2e.test.sh +112 -0
- package/.agents/tests/fm-grok-harness.test.sh +117 -0
- package/.agents/tests/fm-grok-stop-live-e2e.test.sh +219 -0
- package/.agents/tests/fm-guard-stale-banner.test.sh +709 -0
- package/.agents/tests/fm-harness-adapter-instructions-live-e2e.test.sh +134 -0
- package/.agents/tests/fm-harness-adapter-references.test.sh +30 -0
- package/.agents/tests/fm-harness-liveness-drift-live-e2e.test.sh +151 -0
- package/.agents/tests/fm-herdr-lab.test.sh +243 -0
- package/.agents/tests/fm-herdr-session-cleanup-e2e.test.sh +146 -0
- package/.agents/tests/fm-herdr-session-cleanup.test.sh +329 -0
- package/.agents/tests/fm-herdr-submit-confirm-live-e2e.test.sh +125 -0
- package/.agents/tests/fm-herdr-version-floor-live-e2e.test.sh +144 -0
- package/.agents/tests/fm-home-summary-refresh.test.sh +971 -0
- package/.agents/tests/fm-inactive-reconcile.test.sh +485 -0
- package/.agents/tests/fm-kimi-harness.test.sh +687 -0
- package/.agents/tests/fm-lint-workflows.test.sh +532 -0
- package/.agents/tests/fm-lint.test.sh +1023 -0
- package/.agents/tests/fm-muse-harness.test.sh +932 -0
- package/.agents/tests/fm-muse-signals-live-e2e.test.sh +205 -0
- package/.agents/tests/fm-no-mistakes-required.test.sh +72 -0
- package/.agents/tests/fm-on.test.sh +507 -0
- package/.agents/tests/fm-opencode-primary-live-e2e.test.sh +357 -0
- package/.agents/tests/fm-operational-input.test.sh +160 -0
- package/.agents/tests/fm-peek-remote.test.sh +110 -0
- package/.agents/tests/fm-pending-reply.test.sh +1288 -0
- package/.agents/tests/fm-pi-branch-extension.test.sh +3182 -0
- package/.agents/tests/fm-pi-branch-live-e2e.test.sh +527 -0
- package/.agents/tests/fm-pi-primary-live-e2e.test.sh +345 -0
- package/.agents/tests/fm-pi-primary-types.test.sh +68 -0
- package/.agents/tests/fm-pi-watch-extension.test.sh +2843 -0
- package/.agents/tests/fm-pr-check-security.test.sh +2140 -0
- package/.agents/tests/fm-pr-merge.test.sh +2135 -0
- package/.agents/tests/fm-procevent-quota.test.sh +225 -0
- package/.agents/tests/fm-procevent-when.test.sh +406 -0
- package/.agents/tests/fm-procevent.test.sh +1745 -0
- package/.agents/tests/fm-project-origin.test.sh +110 -0
- package/.agents/tests/fm-public-followup.test.sh +2272 -0
- package/.agents/tests/fm-quota-array-dispatch-live-e2e.test.sh +526 -0
- package/.agents/tests/fm-quota-choose.test.sh +626 -0
- package/.agents/tests/fm-remote-backlog-handoff.test.sh +453 -0
- package/.agents/tests/fm-remote-doctor.test.sh +625 -0
- package/.agents/tests/fm-remote-entrypoint.test.sh +59 -0
- package/.agents/tests/fm-remote-job-orphan-reap.test.sh +205 -0
- package/.agents/tests/fm-remote-job.test.sh +768 -0
- package/.agents/tests/fm-remote-reply.test.sh +520 -0
- package/.agents/tests/fm-remote-secondmate-lifecycle-e2e.test.sh +1256 -0
- package/.agents/tests/fm-remote-secondmate-parent-binding.test.sh +326 -0
- package/.agents/tests/fm-remote-secondmate-trace-context.test.sh +310 -0
- package/.agents/tests/fm-remote-transport-lanes.test.sh +431 -0
- package/.agents/tests/fm-review-diff.test.sh +176 -0
- package/.agents/tests/fm-secondmate-harness.test.sh +2600 -0
- package/.agents/tests/fm-secondmate-lifecycle-e2e.test.sh +259 -0
- package/.agents/tests/fm-secondmate-liveness.test.sh +559 -0
- package/.agents/tests/fm-secondmate-reconcile.test.sh +732 -0
- package/.agents/tests/fm-secondmate-safety.test.sh +2972 -0
- package/.agents/tests/fm-secondmate-sync.test.sh +913 -0
- package/.agents/tests/fm-send-inbox-doorbell-live-e2e.test.sh +206 -0
- package/.agents/tests/fm-send-inbox.test.sh +352 -0
- package/.agents/tests/fm-send-popup-settle.test.sh +167 -0
- package/.agents/tests/fm-send-remote-delivery.test.sh +796 -0
- package/.agents/tests/fm-send-resolve-key.test.sh +551 -0
- package/.agents/tests/fm-send-secondmate-marker-herdr-e2e.test.sh +187 -0
- package/.agents/tests/fm-send-secondmate-marker.test.sh +278 -0
- package/.agents/tests/fm-send-settle.test.sh +147 -0
- package/.agents/tests/fm-send-strict.test.sh +241 -0
- package/.agents/tests/fm-session-lock-ancestry.test.sh +365 -0
- package/.agents/tests/fm-session-start.test.sh +2515 -0
- package/.agents/tests/fm-sessionstart-hook-live-e2e.test.sh +631 -0
- package/.agents/tests/fm-sessionstart-instruction-refresh-live-e2e.test.sh +230 -0
- package/.agents/tests/fm-sessionstart-nudge.test.sh +1039 -0
- package/.agents/tests/fm-shared-captain-inheritance.test.sh +404 -0
- package/.agents/tests/fm-spawn-batch.test.sh +148 -0
- package/.agents/tests/fm-spawn-dispatch-profile.test.sh +827 -0
- package/.agents/tests/fm-spawn-pool-base-freshen.test.sh +440 -0
- package/.agents/tests/fm-spawn-worktree-settle.test.sh +147 -0
- package/.agents/tests/fm-startup-memory-budget.test.sh +333 -0
- package/.agents/tests/fm-startup-network.test.sh +712 -0
- package/.agents/tests/fm-stow-cascade.test.sh +370 -0
- package/.agents/tests/fm-subagent-pretool-check.test.sh +291 -0
- package/.agents/tests/fm-supervision-events.test.sh +156 -0
- package/.agents/tests/fm-supervision-instructions.test.sh +189 -0
- package/.agents/tests/fm-tangle-guard.test.sh +277 -0
- package/.agents/tests/fm-task-delivery.test.sh +420 -0
- package/.agents/tests/fm-task-inbox.test.sh +513 -0
- package/.agents/tests/fm-teardown-endpoint-safety.test.sh +374 -0
- package/.agents/tests/fm-teardown.test.sh +2665 -0
- package/.agents/tests/fm-test-fixture-cleanup.test.sh +172 -0
- package/.agents/tests/fm-test-fixtures.test.sh +132 -0
- package/.agents/tests/fm-test-isolation-proof.test.sh +278 -0
- package/.agents/tests/fm-test-run.test.sh +1196 -0
- package/.agents/tests/fm-tmux-agent-liveness.test.sh +354 -0
- package/.agents/tests/fm-tmux-submit-busy.test.sh +340 -0
- package/.agents/tests/fm-tool-update-check.test.sh +1044 -0
- package/.agents/tests/fm-trace-context-lib.test.sh +253 -0
- package/.agents/tests/fm-trace-context-spawn.test.sh +581 -0
- package/.agents/tests/fm-transition-lib.test.sh +54 -0
- package/.agents/tests/fm-turnend-guard.test.sh +1822 -0
- package/.agents/tests/fm-update.test.sh +304 -0
- package/.agents/tests/fm-vendor-auth-probe.test.sh +395 -0
- package/.agents/tests/fm-voice-relay.test.sh +4741 -0
- package/.agents/tests/fm-wake-daemon-lifecycle-e2e.test.sh +171 -0
- package/.agents/tests/fm-wake-drain-open-decisions-cursor.test.sh +356 -0
- package/.agents/tests/fm-wake-drain-open-decisions.test.sh +226 -0
- package/.agents/tests/fm-wake-drain-unread-status.test.sh +379 -0
- package/.agents/tests/fm-wake-queue.test.sh +1224 -0
- package/.agents/tests/fm-watch-arm.test.sh +821 -0
- package/.agents/tests/fm-watch-checkpoint.test.sh +87 -0
- package/.agents/tests/fm-watch-recovery-loop.test.sh +223 -0
- package/.agents/tests/fm-watch-triage.test.sh +3889 -0
- package/.agents/tests/fm-watcher-lock.test.sh +1133 -0
- package/.agents/tests/fm-x-mode.test.sh +3042 -0
- package/.agents/tests/gleipnir-guard-pretool-check.test.sh +51 -0
- package/.agents/tests/gleipnir-machine-lock.test.sh +130 -0
- package/.agents/tests/herdr-test-safety.sh +42 -0
- package/.agents/tests/lib.sh +315 -0
- package/.agents/tests/remote-herdr-fixture.sh +125 -0
- package/.agents/tests/secondmate-helpers.sh +193 -0
- package/.agents/tests/smoke.test.sh +93 -0
- package/.agents/tests/wake-helpers.sh +338 -0
- package/.agents/tests/zellij-test-safety.sh +60 -0
- package/.agents/tools/README.md +10 -0
- package/.agents/tools/bin/tasks-cli +267 -0
- package/.agents/tools/tasks-cli.ts +216 -0
- package/AGENTS.md +454 -0
- package/CHANGELOG.md +881 -0
- package/LICENSE +1 -1
- package/NOTICE +63 -0
- package/README.md +113 -59
- package/RULES/01-domains.md +51 -0
- package/RULES/02-agents.md +78 -0
- package/RULES/03-houses.md +61 -0
- package/RULES/04-hoard.md +65 -0
- package/RULES/05-platforms.md +67 -0
- package/RULES/06-append-only.md +58 -0
- package/RULES/07-config.md +45 -0
- package/RULES/08-delivery-gate.md +75 -0
- package/RULES/README.md +20 -0
- package/bin/README.md +107 -0
- package/bin/a2a-mcp.sh +98 -0
- package/bin/a2a-serve.py +103 -0
- package/bin/a2a-serve.sh +8 -0
- package/bin/a2a-talk.sh +50 -0
- package/bin/a2abridge-ensure.sh +108 -0
- package/bin/agent-run.sh +43 -0
- package/bin/agents-config.sh +233 -0
- package/bin/apodex-smoke-test.sh +92 -0
- package/bin/bifrost-bridge.sh +112 -0
- package/bin/bifrost-ingress.sh +77 -0
- package/bin/bootstrap-macos.sh +107 -0
- package/bin/bootstrap-windows.ps1 +112 -0
- package/bin/branch-guard.sh +64 -0
- package/bin/brokk +73 -0
- package/bin/brokk-classify-lib.sh +1770 -0
- package/bin/brokk-lease-lib.sh +222 -0
- package/bin/brokk-lease.sh +193 -0
- package/bin/brokk-lint.sh +65 -0
- package/bin/brokk-send.sh +38 -0
- package/bin/brokk-update.sh +9 -0
- package/bin/brokk-wake-grant.sh +127 -0
- package/bin/brokk-wake-lib.sh +1849 -0
- package/bin/changelog-guard.sh +165 -0
- package/bin/crash-sense.sh +160 -0
- package/bin/desktop-place.sh +224 -0
- package/bin/docs-guard.sh +39 -0
- package/bin/editor-place.sh +207 -0
- package/bin/eindri-acclaim.sh +34 -0
- package/bin/eindri-control.sh +28 -0
- package/bin/eindri-role.sh +83 -0
- package/bin/eindri-seen.sh +41 -0
- package/bin/eindri-send.sh +15 -0
- package/bin/eindri-start.sh +85 -0
- package/bin/eindri-watch.sh +57 -0
- package/bin/einherjar-spawn.sh +576 -0
- package/bin/eir-doctor.sh +112 -0
- package/bin/erindi-brief.sh +271 -0
- package/bin/fleet-apply.sh +68 -0
- package/bin/github-deploy.sh +81 -0
- package/bin/gjallarhorn-expose.sh +102 -0
- package/bin/gjallarhorn-notify.sh +58 -0
- package/bin/gjallarhorn-purge.sh +54 -0
- package/bin/gjallarhorn-tunnel.sh +72 -0
- package/bin/gleipnir-lock-lib.sh +284 -0
- package/bin/groa-update.sh +122 -0
- package/bin/hall-snapshot.sh +92 -0
- package/bin/hamr-harness.sh +246 -0
- package/bin/heimdall-ssh-keys.sh +374 -0
- package/bin/herdr-agents.py +44 -0
- package/bin/herdr-ensure.sh +104 -0
- package/bin/herdr-run.sh +431 -0
- package/bin/hermes-ensure.sh +88 -0
- package/bin/hoard-lib.sh +25 -0
- package/bin/hodd.sh +78 -0
- package/bin/host-sense.sh +199 -0
- package/bin/huginn-research-worker.sh +108 -0
- package/bin/local-model-lock.sh +51 -0
- package/bin/mimir-bridge.py +226 -0
- package/bin/mimir-bridge.sh +87 -0
- package/bin/mimir-ingest.sh +131 -0
- package/bin/mimir.sh +113 -0
- package/bin/mjollnir-webhook.sh +59 -0
- package/bin/mjollnir.sh +85 -0
- package/bin/model-hardware.sh +82 -0
- package/bin/model-resolve.sh +147 -0
- package/bin/models-detect.sh +178 -0
- package/bin/nornir-cron-start.sh +162 -0
- package/bin/nornir-job-daily-briefing.sh +191 -0
- package/bin/nornir-job-git-sync.sh +152 -0
- package/bin/nornir-job-memory-housekeeping.sh +114 -0
- package/bin/nornir-job-observer.sh +166 -0
- package/bin/nornir-job-skillopt-sleep.sh +76 -0
- package/bin/omarchy-hook-install.sh +60 -0
- package/bin/omarchy-install.sh +148 -0
- package/bin/omarchy-plugins.sh +174 -0
- package/bin/omarchy-sense.sh +149 -0
- package/bin/perm-guard.sh +30 -0
- package/bin/pi-agent.sh +68 -0
- package/bin/pi-ensure.sh +77 -0
- package/bin/pi-local.sh +37 -0
- package/bin/pi-seat.sh +63 -0
- package/bin/prereq-ensure.sh +166 -0
- package/bin/private-guard.sh +38 -0
- package/bin/project-git.sh +55 -0
- package/bin/public-guard.sh +70 -0
- package/bin/ratatoskr.sh +49 -0
- package/bin/realm-lib.sh +32 -0
- package/bin/repo-scrub.sh +62 -0
- package/bin/rodd-operational-input.sh +250 -0
- package/bin/runes-append.sh +198 -0
- package/bin/saga-session-start.sh +172 -0
- package/bin/saga-sessionstart-run.sh +79 -0
- package/bin/saga-wake-drain.sh +41 -0
- package/bin/secret-guard.sh +63 -0
- package/bin/sessrumnir-ensure.sh +100 -0
- package/bin/sessrumnir-sync.sh +237 -0
- package/bin/sessrumnir.sh +91 -0
- package/bin/skillopt-setup.sh +76 -0
- package/bin/skuld-branch-outcome.sh +203 -0
- package/bin/skuld-branch-prompt.sh +101 -0
- package/bin/smidja-bootstrap.sh +90 -0
- package/bin/smidja-observe.sh +86 -0
- package/bin/syn-arm-pretool-check.sh +57 -0
- package/bin/syn-asset-pretool-check.sh +76 -0
- package/bin/syn-cd-pretool-check.sh +28 -0
- package/bin/syn-guard-pretool-check.sh +83 -0
- package/bin/syn-turnend-guard.sh +36 -0
- package/bin/syn-watch-arm.sh +99 -0
- package/bin/tailscale-sync.sh +190 -0
- package/bin/telegram-bot.sh +69 -0
- package/bin/toolchain.sh +74 -0
- package/bin/utgard.sh +111 -0
- package/bin/valhalla.sh +115 -0
- package/bin/valknut-load.sh +260 -0
- package/bin/vor-crew-state.sh +124 -0
- package/bin/wedge-notify.sh +47 -0
- package/bin/workspace-provision.sh +80 -0
- package/bin/workspace-rag.sh +150 -0
- package/bin/wyrd-db.sh +47 -0
- package/bin/yggdrasil.sh +152 -0
- package/bin/ymir-edit.sh +145 -0
- package/bin/ymir-install.sh +641 -0
- package/bin/ymir-invite.sh +93 -0
- package/bin/ymir-isolation.sh +88 -0
- package/bin/ymir-migrate.sh +61 -0
- package/bin/ymir-platform.sh +348 -0
- package/bin/ymir-say.sh +97 -0
- package/bin/ymir-setup-auth.sh +108 -0
- package/bin/ymir-validate.sh +173 -0
- package/bin/ymir.js +74 -16
- package/docs/Architecture.md +339 -0
- package/docs/design.md +351 -0
- package/docs/installations/README.md +63 -0
- package/docs/installations/ymir-home.md +93 -0
- package/docs/integration.md +105 -0
- package/docs/lore.md +1056 -0
- package/docs/research/agent-harnesses.md +290 -0
- package/docs/runbooks/README.md +19 -0
- package/docs/runbooks/a2a.md +91 -0
- package/docs/runbooks/agent-permissions.md +99 -0
- package/docs/runbooks/agents.md +57 -0
- package/docs/runbooks/models.md +69 -0
- package/docs/runbooks/realm-onboarding.md +60 -0
- package/docs/runbooks/secrets-and-hoard.md +60 -0
- package/docs/runbooks/updates-and-migrations.md +110 -0
- package/docs/runbooks/worktrees-isolation.md +72 -0
- package/docs/session-start.md +105 -0
- package/docs/skillopt-integration.md +321 -0
- package/docs/sync-system.md +66 -0
- package/docs/workspaces.md +104 -0
- package/docs/ymir-rut.md +260 -0
- package/package.json +44 -14
- package/packaging/build.sh +74 -0
- package/packaging/macos/Ymir Installer.command +43 -0
- package/packaging/windows/ymir-setup.nsi +74 -0
- package/scripts/README.md +22 -0
- package/scripts/electron.sh +210 -0
- package/scripts/start.sh +167 -0
- package/scripts/stop.sh +113 -0
- package/install.sh +0 -35
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fm-captain-hold.sh - deterministic mechanics for tasks held for the captain.
|
|
3
|
+
#
|
|
4
|
+
# The semantic policy is owned once by
|
|
5
|
+
# .agents/skills/captain-hold-lifecycle/SKILL.md. This script never reads
|
|
6
|
+
# report, visual-review, chat, or terminal prose to guess whether the captain
|
|
7
|
+
# owes an answer. The invoking agent decides what is genuinely waiting on the
|
|
8
|
+
# captain; this script supplies guarded creation, a durable record of what the
|
|
9
|
+
# captain actually said, the investigation completion gate, and the one
|
|
10
|
+
# keyed-answer intake every channel feeds.
|
|
11
|
+
#
|
|
12
|
+
# There is no separate decision type. A captain call is an ordinary backlog
|
|
13
|
+
# task held for the captain (`tasks-axi hold <id> --kind captain`), and its
|
|
14
|
+
# identity is simply the task id. Older installs created derived
|
|
15
|
+
# `<origin>-decision-<key>` identities through bin/fm-decision-hold.sh; those
|
|
16
|
+
# rows are already plain task ids, so they keep working here unchanged, and
|
|
17
|
+
# the legacy inputs noted below resolve them without a migration.
|
|
18
|
+
# All backlog mutations run in the active FM_HOME, which keeps main-home and
|
|
19
|
+
# secondmate-home ownership aligned with the work that discovered the call.
|
|
20
|
+
#
|
|
21
|
+
# Usage:
|
|
22
|
+
# fm-captain-hold.sh hold <task-id> --reason <reason> \
|
|
23
|
+
# [--title <title>] [--repo <repo>] [--origin <origin-id>] [--until YYYY-MM-DD]
|
|
24
|
+
# fm-captain-hold.sh answer <task-id> --decision-file <path> [--release]
|
|
25
|
+
# fm-captain-hold.sh answers [<legacy-origin> | --any-origin] --source <provenance> (keyed answers on stdin)
|
|
26
|
+
# fm-captain-hold.sh bind <source-id> [<legacy-origin> | --any-origin]
|
|
27
|
+
# fm-captain-hold.sh unbind <source-id>
|
|
28
|
+
# fm-captain-hold.sh binding <source-id>
|
|
29
|
+
# fm-captain-hold.sh complete <origin-id> (--none | <task-id>...)
|
|
30
|
+
# fm-captain-hold.sh verify <origin-id>
|
|
31
|
+
# fm-captain-hold.sh diverged
|
|
32
|
+
#
|
|
33
|
+
# `hold` places an existing task under an active captain hold, or creates the
|
|
34
|
+
# task first when no work item exists to hold (--title required to create; the
|
|
35
|
+
# optional --origin records provenance in the new task's body and supplies the
|
|
36
|
+
# default repo from that origin's metadata). Prefer holding the work item the
|
|
37
|
+
# question gates over minting a new row. Repeating `hold` with the same id is
|
|
38
|
+
# idempotent; a task already closed is refused rather than reopened. `--until`
|
|
39
|
+
# records the captain's own deferral date through `tasks-axi hold --until`, so
|
|
40
|
+
# a "revisit later" answer is stored as a date instead of a live card.
|
|
41
|
+
#
|
|
42
|
+
# `answer` records the captain's exact words and closes the call in the same
|
|
43
|
+
# act. It requires a non-empty captain decision file of at most 8192 bytes,
|
|
44
|
+
# writes a resolution block at the top of the task body (the previous body is
|
|
45
|
+
# preserved below the block and archived through tasks-axi --archive-body),
|
|
46
|
+
# then closes the task with `tasks-axi done` - or, with `--release`, lifts the
|
|
47
|
+
# hold with `tasks-axi unhold` so a captain-gated WORK item resumes instead of
|
|
48
|
+
# closing. An exact retry is idempotent only when its requested close mode
|
|
49
|
+
# matches the newest record; a changed decision or a mode mismatch is rejected.
|
|
50
|
+
# A re-held task may record a new answer on top. On a task already closed outside this script,
|
|
51
|
+
# `answer` records the missing resolution block (the old `repair` path) only
|
|
52
|
+
# when the task still carries the captain-hold provenance tasks-axi preserves
|
|
53
|
+
# through a close, so an ordinary finished task cannot be dressed up as an
|
|
54
|
+
# answered captain call. A hold that expired by date (`--until` in the past) is
|
|
55
|
+
# still answerable: the surviving hold annotations, not tasks-axi's live
|
|
56
|
+
# `held:` bit, prove the captain owned it.
|
|
57
|
+
#
|
|
58
|
+
# ONE KEYED-ANSWER INTAKE, FED BY EVERY CHANNEL.
|
|
59
|
+
# "A keyed answer closes its matching captain-held task" is a single
|
|
60
|
+
# capability, owned here and nowhere else. `answers` reads
|
|
61
|
+
# `<task-id>\t<answer>\t<label>[\t<mode>]` lines on stdin and closes each named
|
|
62
|
+
# task through the very same `answer` path above, so every guard applies
|
|
63
|
+
# identically no matter which channel the answer arrived on. The key IS the
|
|
64
|
+
# task id - no identity arithmetic. The optional fourth field selects the close:
|
|
65
|
+
# empty or `done` completes the task, `release` lifts the hold so held work
|
|
66
|
+
# resumes; anything else is skipped. A key that names no task, a task that is
|
|
67
|
+
# not held for the captain, or a task already closed is reported as `skipped:`
|
|
68
|
+
# and feeds nothing. A replayed delivery whose answer digest and requested
|
|
69
|
+
# close mode both match the newest record is reported `closed:` and is a no-op;
|
|
70
|
+
# a mode mismatch is skipped. The command exits nonzero when any key was
|
|
71
|
+
# skipped. `--source` is provenance text recorded in the
|
|
72
|
+
# durable decision, never a behavior switch: this command has no per-channel
|
|
73
|
+
# branch and no knowledge of chat, review decks, or any transport.
|
|
74
|
+
# Legacy input: an optional positional origin (or a stored concrete-origin
|
|
75
|
+
# binding) makes a key that names no task fall back to the old
|
|
76
|
+
# `<origin>-decision-<key>` identity, so an in-flight pre-collapse channel
|
|
77
|
+
# keeps closing its rows; `--any-origin` and the stored `(any)` marker mean
|
|
78
|
+
# what an absent origin means and are accepted for the same reason.
|
|
79
|
+
#
|
|
80
|
+
# A channel's ONLY job is to turn whatever it received into those keyed lines
|
|
81
|
+
# and pipe them here. It must never map keys to tasks, build decision records,
|
|
82
|
+
# choose a close mode beyond what its card declared, or close anything itself.
|
|
83
|
+
#
|
|
84
|
+
# `bind`, `unbind`, and `binding` record that a captured-answer SOURCE feeds
|
|
85
|
+
# this intake, for any channel whose answers arrive detached from their origin
|
|
86
|
+
# (a process-event source id, for example). The binding is a private record
|
|
87
|
+
# under `state/decision-bindings/`; a source with no binding feeds nothing, so
|
|
88
|
+
# this whole path is opt-in per source and an unbound source behaves as if it
|
|
89
|
+
# did not exist. `bind` deliberately does not require the source to exist yet,
|
|
90
|
+
# so a channel can be bound BEFORE it is armed. The optional second argument
|
|
91
|
+
# exists only for legacy pre-collapse records and callers: a concrete origin is
|
|
92
|
+
# stored verbatim and used as the composition fallback above, and
|
|
93
|
+
# `--any-origin` stores the same `(any)` marker a plain `bind <source-id>`
|
|
94
|
+
# stores. `binding` prints the stored value verbatim and `answers` accepts it,
|
|
95
|
+
# so the process-event runner's feed seam is unchanged.
|
|
96
|
+
#
|
|
97
|
+
# `complete` is the shared investigation and visual-review completion gate.
|
|
98
|
+
# It attests, in the origin task's metadata, the reviewed inventory of
|
|
99
|
+
# captain-held tasks that carry the origin's unresolved captain calls.
|
|
100
|
+
# `--none` is an explicit semantic attestation that the just-reviewed surface
|
|
101
|
+
# has no unresolved captain call, and is refused while the origin still has an
|
|
102
|
+
# open keyed status decision. With a non-empty inventory, every listed task is
|
|
103
|
+
# verified durable (actively captain-held, or closed with a recorded answer),
|
|
104
|
+
# the inventory is unioned idempotently into the metadata, and every still-open
|
|
105
|
+
# keyed status decision is transferred to its durable owner with a
|
|
106
|
+
# `captain-held [key=...]` status close naming the inventory. Later review
|
|
107
|
+
# passes may add ids. A post-teardown visual review can complete against the
|
|
108
|
+
# surviving report and tasks without recreating task state.
|
|
109
|
+
# `verify` is read-only and is called by scout teardown, so teardown cannot
|
|
110
|
+
# erase a source before this gate has succeeded: every recorded inventory
|
|
111
|
+
# entry must still be durable and no keyed status decision may be open.
|
|
112
|
+
# Metadata compatibility: the attestation keeps the historical
|
|
113
|
+
# `decisions_reviewed=1` and `decision_keys=` keys, and an inventory entry that
|
|
114
|
+
# names no existing task resolves through the legacy `<origin>-decision-<entry>`
|
|
115
|
+
# identity, so pre-collapse metadata written by fm-decision-hold.sh verifies
|
|
116
|
+
# unchanged. An entry that exists as a task id is always that task.
|
|
117
|
+
#
|
|
118
|
+
# `diverged` is the read-only guard over the seam between the two records of
|
|
119
|
+
# one captain call. See "record divergence" beside command_diverged below.
|
|
120
|
+
#
|
|
121
|
+
# Resolution records: the block written into the body names this script, the
|
|
122
|
+
# decision digest, and a `Resolution mode:` of answered, released, or repaired.
|
|
123
|
+
# Records written by the retired fm-decision-hold.sh (routed, declined,
|
|
124
|
+
# answered, repaired) are recognized everywhere a record is read, so nothing
|
|
125
|
+
# already closed needs rewriting.
|
|
126
|
+
set -eu
|
|
127
|
+
|
|
128
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
129
|
+
FM_ROOT="${FM_ROOT_OVERRIDE:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
130
|
+
FM_HOME="${FM_HOME:-${FM_ROOT_OVERRIDE:-$FM_ROOT}}"
|
|
131
|
+
STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
|
|
132
|
+
DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}"
|
|
133
|
+
|
|
134
|
+
# shellcheck source=bin/fm-classify-lib.sh
|
|
135
|
+
# shellcheck disable=SC1091
|
|
136
|
+
. "$SCRIPT_DIR/fm-classify-lib.sh"
|
|
137
|
+
# shellcheck source=bin/fm-tasks-axi-lib.sh
|
|
138
|
+
# shellcheck disable=SC1091
|
|
139
|
+
. "$SCRIPT_DIR/fm-tasks-axi-lib.sh"
|
|
140
|
+
# shellcheck source=bin/fm-wake-lib.sh
|
|
141
|
+
# shellcheck disable=SC1091
|
|
142
|
+
. "$SCRIPT_DIR/fm-wake-lib.sh"
|
|
143
|
+
|
|
144
|
+
CAPTAIN_META_LOCK=
|
|
145
|
+
CAPTAIN_META_LOCK_HELD=0
|
|
146
|
+
captain_hold_cleanup() {
|
|
147
|
+
if [ "$CAPTAIN_META_LOCK_HELD" = 1 ]; then
|
|
148
|
+
fm_lock_release "$CAPTAIN_META_LOCK" || true
|
|
149
|
+
CAPTAIN_META_LOCK_HELD=0
|
|
150
|
+
fi
|
|
151
|
+
}
|
|
152
|
+
trap captain_hold_cleanup EXIT
|
|
153
|
+
|
|
154
|
+
usage() {
|
|
155
|
+
awk '
|
|
156
|
+
NR == 1 { next }
|
|
157
|
+
/^#/ { sub(/^# ?/, ""); print; next }
|
|
158
|
+
{ exit }
|
|
159
|
+
' "$0"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fail() {
|
|
163
|
+
printf 'fm-captain-hold: %s\n' "$*" >&2
|
|
164
|
+
exit 1
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
validate_slug() { # <label> <value>
|
|
168
|
+
local label=$1 value=$2
|
|
169
|
+
case "$value" in
|
|
170
|
+
''|*[!A-Za-z0-9._-]*) fail "$label must be a non-empty privacy-safe slug: $value" ;;
|
|
171
|
+
esac
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
validate_one_line() { # <label> <value>
|
|
175
|
+
local label=$1 value=$2
|
|
176
|
+
[ -n "$value" ] || fail "$label must not be empty"
|
|
177
|
+
case "$value" in
|
|
178
|
+
*$'\n'*|*$'\r'*) fail "$label must be one line" ;;
|
|
179
|
+
esac
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
sha256_text() { # <text>
|
|
183
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
184
|
+
printf '%s' "$1" | shasum -a 256 | awk '{print $1}'
|
|
185
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
186
|
+
printf '%s' "$1" | sha256sum | awk '{print $1}'
|
|
187
|
+
else
|
|
188
|
+
fail "shasum or sha256sum is required"
|
|
189
|
+
fi
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# The legacy derived identity older installs minted for a captain call.
|
|
193
|
+
# Kept only to resolve pre-collapse rows, metadata entries, and channel keys.
|
|
194
|
+
legacy_hold_id() { # <origin-id> <key>
|
|
195
|
+
printf '%s-decision-%s' "$1" "$2"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# The legacy any-origin binding marker. Slug validation rejects parentheses, so
|
|
199
|
+
# no real origin id or task id can collide with it.
|
|
200
|
+
BINDING_ANY='(any)'
|
|
201
|
+
|
|
202
|
+
DECISION_TEXT=''
|
|
203
|
+
DECISION_DIGEST=''
|
|
204
|
+
|
|
205
|
+
load_decision() { # <path>; sets DECISION_TEXT and DECISION_DIGEST
|
|
206
|
+
local path=$1 decision
|
|
207
|
+
[ -n "$path" ] || fail "--decision-file is required"
|
|
208
|
+
[ -f "$path" ] || fail "decision file does not exist: $path"
|
|
209
|
+
decision=$(cat "$path")
|
|
210
|
+
[ -n "$decision" ] || fail "decision file must not be empty"
|
|
211
|
+
[ "$(printf '%s' "$decision" | LC_ALL=C wc -c | tr -d ' ')" -le 8192 ] \
|
|
212
|
+
|| fail "decision file exceeds 8192 bytes"
|
|
213
|
+
DECISION_TEXT=$decision
|
|
214
|
+
DECISION_DIGEST=$(sha256_text "$decision")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
tasks_axi() {
|
|
218
|
+
(cd "$FM_HOME" && tasks-axi "$@")
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
require_tasks_axi() {
|
|
222
|
+
fm_tasks_axi_compatible || fail "compatible tasks-axi is required"
|
|
223
|
+
tasks-axi hold --help 2>&1 | grep -F -- '--kind captain' >/dev/null \
|
|
224
|
+
|| fail "tasks-axi does not expose the captain-hold contract"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
task_show() { # <id>
|
|
228
|
+
tasks_axi show "$1" --full 2>/dev/null
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
show_field() { # <show-output> <field>
|
|
232
|
+
local output=$1 field=$2
|
|
233
|
+
printf '%s\n' "$output" | sed -n "s/^ $field: //p" | head -1
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
decode_shown_value() { # <shown-field>
|
|
237
|
+
local value=$1
|
|
238
|
+
case "$value" in
|
|
239
|
+
\"*\")
|
|
240
|
+
printf '%s' "$value" | perl -MJSON::PP -e '
|
|
241
|
+
local $/;
|
|
242
|
+
my $value = decode_json(<STDIN>);
|
|
243
|
+
binmode STDOUT, ":raw";
|
|
244
|
+
utf8::encode($value) if utf8::is_utf8($value);
|
|
245
|
+
print $value;
|
|
246
|
+
'
|
|
247
|
+
;;
|
|
248
|
+
*) printf '%s' "$value" ;;
|
|
249
|
+
esac
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Decode show-encoded scalar fields and normalize the empty marker.
|
|
253
|
+
show_field_value() { # <show-output> <field>
|
|
254
|
+
local value
|
|
255
|
+
value=$(decode_shown_value "$(show_field "$1" "$2")")
|
|
256
|
+
[ "$value" != '-' ] || value=''
|
|
257
|
+
printf '%s' "$value"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
origin_exists_here() { # <origin-id>
|
|
261
|
+
[ -f "$STATE/$1.meta" ] && return 0
|
|
262
|
+
[ -f "$DATA/$1/report.md" ] && return 0
|
|
263
|
+
task_show "$1" >/dev/null 2>&1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
list_has_key() { # <comma-list> <key>
|
|
267
|
+
case ",$1," in
|
|
268
|
+
*",$2,"*) return 0 ;;
|
|
269
|
+
*) return 1 ;;
|
|
270
|
+
esac
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
sorted_key_union() { # <comma-list> <newline-or-space-separated-new-keys>
|
|
274
|
+
local existing=$1 new=$2
|
|
275
|
+
{
|
|
276
|
+
printf '%s\n' "$existing" | tr ',' '\n'
|
|
277
|
+
printf '%s\n' "$new" | tr ' ' '\n'
|
|
278
|
+
} | sed '/^$/d' | LC_ALL=C sort -u | paste -sd, -
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
meta_value() { # <meta> <key>
|
|
282
|
+
grep "^$2=" "$1" 2>/dev/null | tail -1 | cut -d= -f2- || true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
origin_open_decisions() { # <origin-id>
|
|
286
|
+
local origin=$1 meta="$STATE/$1.meta" status_file="$STATE/$1.status" open kind last verb
|
|
287
|
+
open=$(status_open_decisions "$status_file")
|
|
288
|
+
[ -n "$open" ] || return 0
|
|
289
|
+
[ -f "$meta" ] || { printf '%s' "$open"; return 0; }
|
|
290
|
+
kind=$(meta_value "$meta" kind)
|
|
291
|
+
[ -n "$kind" ] || kind=ship
|
|
292
|
+
if [ "$kind" != secondmate ]; then
|
|
293
|
+
last=$(last_status_line "$status_file")
|
|
294
|
+
verb=$(status_line_verb "$last")
|
|
295
|
+
case "$verb" in
|
|
296
|
+
done|failed) return 0 ;;
|
|
297
|
+
esac
|
|
298
|
+
fi
|
|
299
|
+
printf '%s' "$open"
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# A resolution record written by this script or by the retired
|
|
303
|
+
# fm-decision-hold.sh. Both carry the same leader-then-captain-decision shape.
|
|
304
|
+
body_has_resolution_record() { # <task-body>
|
|
305
|
+
case "$1" in
|
|
306
|
+
*"Resolution recorded by fm-captain-hold."*"Captain decision:"*) return 0 ;;
|
|
307
|
+
*"Resolution recorded by fm-decision-hold."*"Captain decision:"*) return 0 ;;
|
|
308
|
+
esac
|
|
309
|
+
return 1
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# The recorded decision digest of either record format, from the show-escaped
|
|
313
|
+
# body (multi-line bodies print as one quoted line with \n escapes). Records
|
|
314
|
+
# are prepended, so the first match is the newest record.
|
|
315
|
+
recorded_decision_digest() { # <task-body>
|
|
316
|
+
local rest=$1
|
|
317
|
+
case "$rest" in
|
|
318
|
+
*"Decision digest: "*) rest=${rest#*"Decision digest: "} ;;
|
|
319
|
+
*) return 1 ;;
|
|
320
|
+
esac
|
|
321
|
+
rest=${rest%%\\n*}
|
|
322
|
+
rest=${rest%%$'\n'*}
|
|
323
|
+
printf '%s' "$rest"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# The newest record's `Resolution mode:` value; empty for a record predating it.
|
|
327
|
+
recorded_resolution_mode() { # <task-body>
|
|
328
|
+
local rest=$1
|
|
329
|
+
case "$rest" in
|
|
330
|
+
*"Resolution mode: "*) rest=${rest#*"Resolution mode: "} ;;
|
|
331
|
+
*) return 1 ;;
|
|
332
|
+
esac
|
|
333
|
+
rest=${rest%%\\n*}
|
|
334
|
+
rest=${rest%%$'\n'*}
|
|
335
|
+
printf '%s' "$rest"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
resolution_block() { # <mode>
|
|
339
|
+
printf 'Resolution recorded by fm-captain-hold.\nDecision digest: %s\nResolution mode: %s\n\nCaptain decision:\n%s\n' \
|
|
340
|
+
"$DECISION_DIGEST" "$1" "$DECISION_TEXT"
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
# Durable state of one captain call: an active captain hold (annotations
|
|
344
|
+
# surviving even when a date gate has expired) or a recorded captain answer.
|
|
345
|
+
verify_hold_durable() { # <task-id>
|
|
346
|
+
local id=$1 show state hold_kind body
|
|
347
|
+
show=$(task_show "$id") || fail "captain-held task $id is absent from $FM_HOME/data/backlog.md"
|
|
348
|
+
state=$(show_field "$show" state)
|
|
349
|
+
hold_kind=$(show_field_value "$show" hold_kind)
|
|
350
|
+
body=$(show_field "$show" body)
|
|
351
|
+
if body_has_resolution_record "$body"; then
|
|
352
|
+
return 0
|
|
353
|
+
fi
|
|
354
|
+
if [ "$state" != "done" ] && [ "$hold_kind" = captain ]; then
|
|
355
|
+
return 0
|
|
356
|
+
fi
|
|
357
|
+
fail "captain-held task $id is neither held for the captain nor closed with a recorded captain answer"
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
# Resolve one inventory entry or channel key to the task that carries it: the
|
|
361
|
+
# exact task id when it exists, else the legacy derived identity.
|
|
362
|
+
resolve_entry() { # <origin-or-empty> <entry>; prints the resolved id or fails
|
|
363
|
+
local origin=$1 entry=$2 legacy
|
|
364
|
+
if task_show "$entry" >/dev/null 2>&1; then
|
|
365
|
+
printf '%s' "$entry"
|
|
366
|
+
return 0
|
|
367
|
+
fi
|
|
368
|
+
if [ -n "$origin" ] && [ "$origin" != "$BINDING_ANY" ]; then
|
|
369
|
+
legacy=$(legacy_hold_id "$origin" "$entry")
|
|
370
|
+
if task_show "$legacy" >/dev/null 2>&1; then
|
|
371
|
+
printf '%s' "$legacy"
|
|
372
|
+
return 0
|
|
373
|
+
fi
|
|
374
|
+
fail "no captain-held task $entry and no legacy identity $legacy in $FM_HOME/data/backlog.md"
|
|
375
|
+
fi
|
|
376
|
+
fail "no captain-held task $entry in $FM_HOME/data/backlog.md"
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
command_hold() {
|
|
380
|
+
local id=${1:-} title='' reason='' repo='' origin='' until='' show state existing_title body='' hold_kind
|
|
381
|
+
[ "$#" -ge 1 ] || { usage >&2; exit 2; }
|
|
382
|
+
shift
|
|
383
|
+
while [ "$#" -gt 0 ]; do
|
|
384
|
+
case "$1" in
|
|
385
|
+
--title) shift; title=${1:-} ;;
|
|
386
|
+
--reason) shift; reason=${1:-} ;;
|
|
387
|
+
--repo) shift; repo=${1:-} ;;
|
|
388
|
+
--origin) shift; origin=${1:-} ;;
|
|
389
|
+
--until) shift; until=${1:-} ;;
|
|
390
|
+
*) usage >&2; exit 2 ;;
|
|
391
|
+
esac
|
|
392
|
+
shift
|
|
393
|
+
done
|
|
394
|
+
validate_slug task-id "$id"
|
|
395
|
+
validate_one_line reason "$reason"
|
|
396
|
+
case "$reason" in *'('*|*')'*) fail "reason must not contain parentheses (tasks-axi hold contract)" ;; esac
|
|
397
|
+
if [ -n "$origin" ]; then
|
|
398
|
+
validate_slug origin-id "$origin"
|
|
399
|
+
fi
|
|
400
|
+
if [ -n "$until" ]; then
|
|
401
|
+
case "$until" in
|
|
402
|
+
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) : ;;
|
|
403
|
+
*) fail "--until must be a YYYY-MM-DD date: $until" ;;
|
|
404
|
+
esac
|
|
405
|
+
fi
|
|
406
|
+
require_tasks_axi
|
|
407
|
+
if show=$(task_show "$id"); then
|
|
408
|
+
state=$(show_field "$show" state)
|
|
409
|
+
[ "$state" != "done" ] \
|
|
410
|
+
|| fail "task $id is already closed; a new captain call needs its own task"
|
|
411
|
+
if [ -n "$title" ]; then
|
|
412
|
+
existing_title=$(show_field_value "$show" title)
|
|
413
|
+
[ "$existing_title" = "$title" ] || fail "existing task $id has a different title"
|
|
414
|
+
fi
|
|
415
|
+
else
|
|
416
|
+
[ -n "$title" ] || fail "--title is required to create task $id"
|
|
417
|
+
validate_one_line title "$title"
|
|
418
|
+
if [ -z "$repo" ] && [ -n "$origin" ] && [ -f "$STATE/$origin.meta" ]; then
|
|
419
|
+
repo=$(meta_value "$STATE/$origin.meta" project)
|
|
420
|
+
repo=${repo%/}
|
|
421
|
+
repo=${repo##*/}
|
|
422
|
+
fi
|
|
423
|
+
[ -n "$repo" ] || repo=firstmate
|
|
424
|
+
validate_one_line repo "$repo"
|
|
425
|
+
[ -z "$origin" ] || body=$(printf 'Origin: %s' "$origin")
|
|
426
|
+
if [ -n "$body" ]; then
|
|
427
|
+
tasks_axi add "$id" "$title" --repo "$repo" --body "$body" >/dev/null \
|
|
428
|
+
|| fail "could not create task $id"
|
|
429
|
+
else
|
|
430
|
+
tasks_axi add "$id" "$title" --repo "$repo" >/dev/null \
|
|
431
|
+
|| fail "could not create task $id"
|
|
432
|
+
fi
|
|
433
|
+
fi
|
|
434
|
+
if [ -n "$until" ]; then
|
|
435
|
+
tasks_axi hold "$id" --reason "$reason" --kind captain --until "$until" >/dev/null \
|
|
436
|
+
|| fail "could not hold task $id for the captain"
|
|
437
|
+
else
|
|
438
|
+
tasks_axi hold "$id" --reason "$reason" --kind captain >/dev/null \
|
|
439
|
+
|| fail "could not hold task $id for the captain"
|
|
440
|
+
fi
|
|
441
|
+
show=$(task_show "$id") || fail "task $id disappeared while holding it"
|
|
442
|
+
hold_kind=$(show_field_value "$show" hold_kind)
|
|
443
|
+
[ "$hold_kind" = captain ] || fail "task $id did not retain its captain hold"
|
|
444
|
+
printf '%s\n' "$id"
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
# Record a resolution block at the top of the task body, preserving the
|
|
448
|
+
# previous body below it and archiving the pristine original.
|
|
449
|
+
write_resolution_record() { # <task-id> <mode> <shown-body>
|
|
450
|
+
local id=$1 mode=$2 body=$3 new_body tmp
|
|
451
|
+
new_body=$(resolution_block "$mode")
|
|
452
|
+
body=$(decode_shown_value "$body") \
|
|
453
|
+
|| fail "could not decode the existing body for $id"
|
|
454
|
+
if [ -n "$body" ]; then
|
|
455
|
+
new_body=$(printf '%s\n\n%s' "$new_body" "$body")
|
|
456
|
+
fi
|
|
457
|
+
tmp=$(umask 077; mktemp "${TMPDIR:-/tmp}/fm-captain-hold-body.XXXXXX") \
|
|
458
|
+
|| fail "cannot stage the resolution record"
|
|
459
|
+
if ! printf '%s\n' "$new_body" > "$tmp"; then
|
|
460
|
+
rm -f -- "$tmp"
|
|
461
|
+
fail "cannot stage the resolution record for $id"
|
|
462
|
+
fi
|
|
463
|
+
if ! tasks_axi update "$id" --body-file "$tmp" --archive-body >/dev/null; then
|
|
464
|
+
rm -f -- "$tmp"
|
|
465
|
+
fail "could not record the captain decision on $id"
|
|
466
|
+
fi
|
|
467
|
+
rm -f -- "$tmp"
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
close_answered() { # <task-id> <release-0-or-1>
|
|
471
|
+
if [ "$2" = 1 ]; then
|
|
472
|
+
tasks_axi unhold "$1" >/dev/null || fail "could not release captain-held task $1"
|
|
473
|
+
else
|
|
474
|
+
tasks_axi "done" "$1" >/dev/null || fail "could not close answered captain-held task $1"
|
|
475
|
+
fi
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
command_answer() {
|
|
479
|
+
local id=${1:-} decision_file='' release=0 show state hold_kind body outcome recorded_mode
|
|
480
|
+
[ "$#" -ge 1 ] || { usage >&2; exit 2; }
|
|
481
|
+
shift
|
|
482
|
+
while [ "$#" -gt 0 ]; do
|
|
483
|
+
case "$1" in
|
|
484
|
+
--decision-file) shift; decision_file=${1:-} ;;
|
|
485
|
+
--release) release=1 ;;
|
|
486
|
+
*) usage >&2; exit 2 ;;
|
|
487
|
+
esac
|
|
488
|
+
shift
|
|
489
|
+
done
|
|
490
|
+
validate_slug task-id "$id"
|
|
491
|
+
load_decision "$decision_file"
|
|
492
|
+
require_tasks_axi
|
|
493
|
+
show=$(task_show "$id") || fail "captain-held task $id is absent from $FM_HOME/data/backlog.md"
|
|
494
|
+
state=$(show_field "$show" state)
|
|
495
|
+
hold_kind=$(show_field_value "$show" hold_kind)
|
|
496
|
+
body=$(show_field "$show" body)
|
|
497
|
+
if [ "$release" = 1 ]; then outcome=released; else outcome=answered; fi
|
|
498
|
+
|
|
499
|
+
if [ "$state" = "done" ]; then
|
|
500
|
+
if body_has_resolution_record "$body"; then
|
|
501
|
+
# An exact compatible retry is an idempotent no-op; drift is rejected.
|
|
502
|
+
[ "$(recorded_decision_digest "$body" || true)" = "$DECISION_DIGEST" ] \
|
|
503
|
+
|| fail "captain-held task $id records a different captain decision"
|
|
504
|
+
recorded_mode=$(recorded_resolution_mode "$body" || true)
|
|
505
|
+
[ "$recorded_mode" != released ] \
|
|
506
|
+
|| fail "task $id records this answer with mode released; a closed task cannot replay that release"
|
|
507
|
+
[ "$release" = 0 ] \
|
|
508
|
+
|| fail "task $id records this answer with mode ${recorded_mode:-unknown}; --release cannot reopen a closed task"
|
|
509
|
+
printf 'answered: %s\n' "$id"
|
|
510
|
+
return 0
|
|
511
|
+
fi
|
|
512
|
+
[ "$release" = 0 ] || fail "task $id is already closed; --release cannot reopen it"
|
|
513
|
+
# Closed outside this script: record the captain's answer retroactively.
|
|
514
|
+
# tasks-axi keeps hold_kind through a close, so it is the surviving proof
|
|
515
|
+
# this really was the captain's item rather than ordinary finished work.
|
|
516
|
+
[ "$hold_kind" = captain ] \
|
|
517
|
+
|| fail "task $id was never held for the captain; nothing to record an answer on"
|
|
518
|
+
write_resolution_record "$id" repaired "$body"
|
|
519
|
+
show=$(task_show "$id") || fail "task $id disappeared while recording the answer"
|
|
520
|
+
[ "$(show_field "$show" state)" = "done" ] || fail "recording the answer reopened closed task $id"
|
|
521
|
+
body_has_resolution_record "$(show_field "$show" body)" \
|
|
522
|
+
|| fail "captain-held task $id did not retain its durable resolution record"
|
|
523
|
+
printf 'repaired: %s\n' "$id"
|
|
524
|
+
return 0
|
|
525
|
+
fi
|
|
526
|
+
|
|
527
|
+
if [ "$hold_kind" = captain ]; then
|
|
528
|
+
# Actively the captain's item (a date-expired hold keeps its annotations
|
|
529
|
+
# and stays answerable). A matching record means an interrupted close to
|
|
530
|
+
# finish; a different digest is a NEW answer on a re-held task and gets
|
|
531
|
+
# its own record on top. Either way the close mode is the caller's flag,
|
|
532
|
+
# checked against an interrupted close's recorded mode so a retry cannot
|
|
533
|
+
# silently flip a release into a close.
|
|
534
|
+
if body_has_resolution_record "$body" \
|
|
535
|
+
&& [ "$(recorded_decision_digest "$body" || true)" = "$DECISION_DIGEST" ]; then
|
|
536
|
+
recorded_mode=$(recorded_resolution_mode "$body" || true)
|
|
537
|
+
case "$recorded_mode" in
|
|
538
|
+
released) [ "$release" = 1 ] || fail "task $id records this answer as a release; retry with --release" ;;
|
|
539
|
+
answered) [ "$release" = 0 ] || fail "task $id records this answer as a close; retry without --release" ;;
|
|
540
|
+
esac
|
|
541
|
+
close_answered "$id" "$release"
|
|
542
|
+
printf '%s: %s\n' "$outcome" "$id"
|
|
543
|
+
return 0
|
|
544
|
+
fi
|
|
545
|
+
write_resolution_record "$id" "$outcome" "$body"
|
|
546
|
+
close_answered "$id" "$release"
|
|
547
|
+
show=$(task_show "$id") || fail "task $id disappeared after closing"
|
|
548
|
+
body_has_resolution_record "$(show_field "$show" body)" \
|
|
549
|
+
|| fail "captain-held task $id did not retain its durable resolution record"
|
|
550
|
+
printf '%s: %s\n' "$outcome" "$id"
|
|
551
|
+
return 0
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
# Not held and not closed: only an already-recorded release replays cleanly.
|
|
555
|
+
if body_has_resolution_record "$body"; then
|
|
556
|
+
recorded_mode=$(recorded_resolution_mode "$body" || true)
|
|
557
|
+
[ "$(recorded_decision_digest "$body" || true)" = "$DECISION_DIGEST" ] \
|
|
558
|
+
|| fail "task $id records a different captain decision with mode ${recorded_mode:-unknown}"
|
|
559
|
+
[ "$recorded_mode" = released ] && [ "$release" = 1 ] \
|
|
560
|
+
|| fail "task $id records this answer with mode ${recorded_mode:-unknown}; replay requires matching --release"
|
|
561
|
+
printf 'released: %s\n' "$id"
|
|
562
|
+
return 0
|
|
563
|
+
fi
|
|
564
|
+
fail "task $id is not held for the captain; hold it first or name the right task"
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
# --- the one keyed-answer intake, and the source bindings that feed it --------
|
|
568
|
+
|
|
569
|
+
BINDING_DIR="$STATE/decision-bindings"
|
|
570
|
+
BINDING_SCHEMA=fm-decision-binding.v1
|
|
571
|
+
|
|
572
|
+
validate_source_id() { # <source-id>
|
|
573
|
+
validate_slug source-id "$1"
|
|
574
|
+
[ "${#1}" -le 64 ] || fail "source-id must be at most 64 characters: $1"
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
binding_path() { printf '%s/%s.origin\n' "$BINDING_DIR" "$1"; }
|
|
578
|
+
|
|
579
|
+
# The stored binding value, or empty when the source is unbound. An unreadable
|
|
580
|
+
# or wrong-schema record is a hard error rather than a silent "unbound":
|
|
581
|
+
# feeding nothing is the safe direction only when it is a deliberate choice,
|
|
582
|
+
# never when it is a corrupted record.
|
|
583
|
+
read_binding() { # <source-id>
|
|
584
|
+
local path origin schema
|
|
585
|
+
path=$(binding_path "$1")
|
|
586
|
+
[ -e "$path" ] || return 0
|
|
587
|
+
[ -f "$path" ] && [ ! -L "$path" ] || fail "decision binding is unsafe: $path"
|
|
588
|
+
schema=$(sed -n 's/^schema=//p' "$path" | head -1)
|
|
589
|
+
[ "$schema" = "$BINDING_SCHEMA" ] || fail "decision binding has an incompatible schema: $path"
|
|
590
|
+
origin=$(sed -n 's/^origin=//p' "$path" | head -1)
|
|
591
|
+
if [ "$origin" != "$BINDING_ANY" ]; then
|
|
592
|
+
case "$origin" in
|
|
593
|
+
''|*[!A-Za-z0-9._-]*) fail "decision binding has an invalid origin id: $path" ;;
|
|
594
|
+
esac
|
|
595
|
+
fi
|
|
596
|
+
printf '%s\n' "$origin"
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
command_bind() {
|
|
600
|
+
local source=${1:-} origin=${2:-} dest tmp
|
|
601
|
+
[ "$#" -ge 1 ] && [ "$#" -le 2 ] || { usage >&2; exit 2; }
|
|
602
|
+
validate_source_id "$source"
|
|
603
|
+
if [ -z "$origin" ] || [ "$origin" = --any-origin ]; then
|
|
604
|
+
origin=$BINDING_ANY
|
|
605
|
+
else
|
|
606
|
+
validate_slug legacy-origin "$origin"
|
|
607
|
+
fi
|
|
608
|
+
(umask 077; mkdir -p "$BINDING_DIR") || fail "cannot create $BINDING_DIR"
|
|
609
|
+
[ -d "$BINDING_DIR" ] && [ ! -L "$BINDING_DIR" ] || fail "decision binding dir is unsafe: $BINDING_DIR"
|
|
610
|
+
dest=$(binding_path "$source")
|
|
611
|
+
tmp=$(umask 077; mktemp "$BINDING_DIR/.origin.XXXXXX") || fail "cannot stage the decision binding"
|
|
612
|
+
if ! { printf 'schema=%s\norigin=%s\n' "$BINDING_SCHEMA" "$origin" > "$tmp" \
|
|
613
|
+
&& chmod 0600 "$tmp" && mv -f -- "$tmp" "$dest"; }; then
|
|
614
|
+
rm -f -- "$tmp"
|
|
615
|
+
fail "cannot record the decision binding for $source"
|
|
616
|
+
fi
|
|
617
|
+
printf 'bound: %s -> %s\n' "$source" "$origin"
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
command_unbind() {
|
|
621
|
+
local source=${1:-}
|
|
622
|
+
[ "$#" -eq 1 ] || { usage >&2; exit 2; }
|
|
623
|
+
validate_source_id "$source"
|
|
624
|
+
rm -f -- "$(binding_path "$source")"
|
|
625
|
+
printf 'unbound: %s\n' "$source"
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
command_binding() {
|
|
629
|
+
local source=${1:-} origin
|
|
630
|
+
[ "$#" -eq 1 ] || { usage >&2; exit 2; }
|
|
631
|
+
validate_source_id "$source"
|
|
632
|
+
origin=$(read_binding "$source") || exit 1
|
|
633
|
+
[ -n "$origin" ] || return 1
|
|
634
|
+
printf '%s\n' "$origin"
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
# The durable captain decision one keyed answer records. Pure function of its
|
|
638
|
+
# inputs, so the same answer delivered twice is idempotent rather than a
|
|
639
|
+
# conflicting decision.
|
|
640
|
+
keyed_decision_text() { # <source> <task-id> <answer> <label>
|
|
641
|
+
printf 'Captain answered this call through %s.\n' "$1"
|
|
642
|
+
printf 'Task: %s\n' "$2"
|
|
643
|
+
printf 'Answer: %s\n' "$3"
|
|
644
|
+
[ -z "$4" ] || printf 'Answer as shown to the captain: %s\n' "$4"
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
legacy_keyed_decision_text() { # <source> <key> <answer> <label>
|
|
648
|
+
printf 'Captain answered this decision through %s.\n' "$1"
|
|
649
|
+
printf 'Decision key: %s\n' "$2"
|
|
650
|
+
printf 'Answer: %s\n' "$3"
|
|
651
|
+
[ -z "$4" ] || printf 'Answer as shown to the captain: %s\n' "$4"
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
sanitize_field() { # <text>
|
|
655
|
+
printf '%s' "$1" | tr '\n\r\t' ' ' | LC_ALL=C tr -d '\000-\037\177' | cut -c1-512
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
command_answers() {
|
|
659
|
+
local origin='' source='' row rest key answer label mode id show state hold_kind body digest legacy_digest legacy_key
|
|
660
|
+
local recorded_digest recorded_mode tmp err closed=0 skipped=0 reason release_flag tab=$'\t'
|
|
661
|
+
while [ "$#" -gt 0 ]; do
|
|
662
|
+
case "$1" in
|
|
663
|
+
--source) shift; source=${1:-} ;;
|
|
664
|
+
--any-origin) origin=$BINDING_ANY ;;
|
|
665
|
+
--*) usage >&2; exit 2 ;;
|
|
666
|
+
*)
|
|
667
|
+
[ -z "$origin" ] || { usage >&2; exit 2; }
|
|
668
|
+
origin=$1
|
|
669
|
+
;;
|
|
670
|
+
esac
|
|
671
|
+
shift
|
|
672
|
+
done
|
|
673
|
+
if [ -n "$origin" ] && [ "$origin" != "$BINDING_ANY" ]; then
|
|
674
|
+
validate_slug legacy-origin "$origin"
|
|
675
|
+
fi
|
|
676
|
+
[ -n "$source" ] || fail "--source provenance is required so the durable decision records where the answer came from"
|
|
677
|
+
source=$(sanitize_field "$source")
|
|
678
|
+
require_tasks_axi
|
|
679
|
+
tmp=$(umask 077; mktemp "${TMPDIR:-/tmp}/fm-keyed-decision.XXXXXX") || fail "cannot stage the captain decision"
|
|
680
|
+
err=$(umask 077; mktemp "${TMPDIR:-/tmp}/fm-keyed-decision-err.XXXXXX") \
|
|
681
|
+
|| { rm -f -- "$tmp"; fail "cannot stage the captain decision diagnostics"; }
|
|
682
|
+
while IFS= read -r row; do
|
|
683
|
+
key=${row%%"$tab"*}
|
|
684
|
+
rest=''
|
|
685
|
+
case "$row" in *"$tab"*) rest=${row#*"$tab"} ;; esac
|
|
686
|
+
answer=${rest%%"$tab"*}
|
|
687
|
+
case "$rest" in *"$tab"*) rest=${rest#*"$tab"} ;; *) rest='' ;; esac
|
|
688
|
+
label=${rest%%"$tab"*}
|
|
689
|
+
case "$rest" in *"$tab"*) mode=${rest#*"$tab"} ;; *) mode='' ;; esac
|
|
690
|
+
[ -n "${key:-}" ] || continue
|
|
691
|
+
case "$key" in *[!A-Za-z0-9._-]*) continue ;; esac
|
|
692
|
+
[ "${#key}" -le 128 ] || continue
|
|
693
|
+
answer=$(sanitize_field "${answer:-}")
|
|
694
|
+
[ -n "$answer" ] || continue
|
|
695
|
+
label=$(sanitize_field "${label:-}")
|
|
696
|
+
release_flag=''
|
|
697
|
+
case "${mode:-}" in
|
|
698
|
+
''|done) : ;;
|
|
699
|
+
release) release_flag=--release ;;
|
|
700
|
+
*)
|
|
701
|
+
printf 'skipped: %s (unknown close mode %s)\n' "$key" "$(sanitize_field "$mode")"
|
|
702
|
+
skipped=$((skipped + 1))
|
|
703
|
+
continue
|
|
704
|
+
;;
|
|
705
|
+
esac
|
|
706
|
+
if ! id=$(resolve_entry "$origin" "$key" 2>/dev/null); then
|
|
707
|
+
printf 'skipped: %s (no captain-held task with that id)\n' "$key"
|
|
708
|
+
skipped=$((skipped + 1))
|
|
709
|
+
continue
|
|
710
|
+
fi
|
|
711
|
+
keyed_decision_text "$source" "$id" "$answer" "$label" > "$tmp" \
|
|
712
|
+
|| fail "cannot stage the captain decision for $id"
|
|
713
|
+
digest=$(sha256_text "$(cat "$tmp")")
|
|
714
|
+
legacy_digest=''
|
|
715
|
+
if [ "$id" != "$key" ]; then
|
|
716
|
+
legacy_key=$key
|
|
717
|
+
elif { [ -z "$origin" ] || [ "$origin" = "$BINDING_ANY" ]; } \
|
|
718
|
+
&& [ "${id#*-decision-}" != "$id" ]; then
|
|
719
|
+
legacy_key=${id#*-decision-}
|
|
720
|
+
else
|
|
721
|
+
legacy_key=''
|
|
722
|
+
fi
|
|
723
|
+
if [ -n "$legacy_key" ]; then
|
|
724
|
+
legacy_digest=$(sha256_text "$(legacy_keyed_decision_text "$source" "$legacy_key" "$answer" "$label")")
|
|
725
|
+
fi
|
|
726
|
+
show=$(task_show "$id") || { printf 'skipped: %s (absent)\n' "$id"; skipped=$((skipped + 1)); continue; }
|
|
727
|
+
state=$(show_field "$show" state)
|
|
728
|
+
hold_kind=$(show_field_value "$show" hold_kind)
|
|
729
|
+
body=$(show_field "$show" body)
|
|
730
|
+
recorded_digest=$(recorded_decision_digest "$body" || true)
|
|
731
|
+
recorded_mode=$(recorded_resolution_mode "$body" || true)
|
|
732
|
+
if body_has_resolution_record "$body" \
|
|
733
|
+
&& { [ "$recorded_digest" = "$digest" ] \
|
|
734
|
+
|| { case "$body" in *"Resolution recorded by fm-decision-hold."*) true ;; *) false ;; esac \
|
|
735
|
+
&& [ -n "$legacy_digest" ] && [ "$recorded_digest" = "$legacy_digest" ]; }; }; then
|
|
736
|
+
if { [ -z "$release_flag" ] && [ "$state" = "done" ] && [ "$recorded_mode" != released ]; } \
|
|
737
|
+
|| { [ "$release_flag" = --release ] && [ "$state" != "done" ] \
|
|
738
|
+
&& [ "$hold_kind" != captain ] && [ "$recorded_mode" = released ]; }; then
|
|
739
|
+
printf 'closed: %s\n' "$id"
|
|
740
|
+
closed=$((closed + 1))
|
|
741
|
+
continue
|
|
742
|
+
fi
|
|
743
|
+
fi
|
|
744
|
+
if [ "$state" = "done" ]; then
|
|
745
|
+
printf 'skipped: %s (already closed)\n' "$id"
|
|
746
|
+
skipped=$((skipped + 1))
|
|
747
|
+
continue
|
|
748
|
+
fi
|
|
749
|
+
if [ "$hold_kind" != captain ]; then
|
|
750
|
+
printf 'skipped: %s (not held for the captain)\n' "$id"
|
|
751
|
+
skipped=$((skipped + 1))
|
|
752
|
+
continue
|
|
753
|
+
fi
|
|
754
|
+
# shellcheck disable=SC2086 # release_flag is empty or a single literal flag.
|
|
755
|
+
if "$0" answer "$id" --decision-file "$tmp" $release_flag </dev/null >/dev/null 2>"$err"; then
|
|
756
|
+
printf 'closed: %s\n' "$id"
|
|
757
|
+
closed=$((closed + 1))
|
|
758
|
+
else
|
|
759
|
+
reason=$(tr -d '\n' < "$err" | sed 's/^fm-captain-hold: //')
|
|
760
|
+
printf 'skipped: %s (%s)\n' "$id" "$reason"
|
|
761
|
+
skipped=$((skipped + 1))
|
|
762
|
+
fi
|
|
763
|
+
done
|
|
764
|
+
rm -f -- "$tmp" "$err"
|
|
765
|
+
printf 'answers: closed=%s skipped=%s\n' "$closed" "$skipped"
|
|
766
|
+
[ "$skipped" -eq 0 ]
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
command_complete() {
|
|
770
|
+
local origin=${1:-} meta previous='' supplied='' keys='' entry key status_file open raw_open has_meta=0 transfer_rc
|
|
771
|
+
[ "$#" -ge 2 ] || { usage >&2; exit 2; }
|
|
772
|
+
validate_slug origin-id "$origin"
|
|
773
|
+
shift
|
|
774
|
+
meta="$STATE/$origin.meta"
|
|
775
|
+
[ -f "$meta" ] && has_meta=1
|
|
776
|
+
if [ "$has_meta" = 1 ]; then
|
|
777
|
+
CAPTAIN_META_LOCK=$(fm_meta_lock_path "$meta") || fail "could not resolve task metadata lock"
|
|
778
|
+
fm_lock_acquire_wait "$CAPTAIN_META_LOCK"
|
|
779
|
+
CAPTAIN_META_LOCK_HELD=1
|
|
780
|
+
[ -f "$meta" ] || fail "task metadata disappeared while recording completion"
|
|
781
|
+
fi
|
|
782
|
+
require_tasks_axi
|
|
783
|
+
origin_exists_here "$origin" || fail "origin $origin is not owned by the active home $FM_HOME"
|
|
784
|
+
if [ "$#" -eq 1 ] && [ "$1" = --none ]; then
|
|
785
|
+
supplied=''
|
|
786
|
+
else
|
|
787
|
+
while [ "$#" -gt 0 ]; do
|
|
788
|
+
[ "$1" != --none ] || fail "--none cannot be combined with task ids"
|
|
789
|
+
validate_slug task-id "$1"
|
|
790
|
+
supplied="${supplied}${supplied:+ }$1"
|
|
791
|
+
shift
|
|
792
|
+
done
|
|
793
|
+
fi
|
|
794
|
+
if [ "$has_meta" = 1 ]; then
|
|
795
|
+
previous=$(meta_value "$meta" decision_keys)
|
|
796
|
+
fi
|
|
797
|
+
keys=$(sorted_key_union "$previous" "$supplied")
|
|
798
|
+
if [ -n "$keys" ]; then
|
|
799
|
+
while IFS= read -r entry; do
|
|
800
|
+
[ -n "$entry" ] || continue
|
|
801
|
+
verify_hold_durable "$(resolve_entry "$origin" "$entry")"
|
|
802
|
+
done <<EOF
|
|
803
|
+
$(printf '%s\n' "$keys" | tr ',' '\n')
|
|
804
|
+
EOF
|
|
805
|
+
fi
|
|
806
|
+
|
|
807
|
+
status_file="$STATE/$origin.status"
|
|
808
|
+
raw_open=$(status_open_decisions "$status_file")
|
|
809
|
+
open=$(origin_open_decisions "$origin")
|
|
810
|
+
if [ -n "$open" ] && [ -z "$keys" ]; then
|
|
811
|
+
fail "origin $origin still has open captain decisions in its status stream; hold a captain task for what remains, or answer them, before attesting --none"
|
|
812
|
+
fi
|
|
813
|
+
|
|
814
|
+
if [ "$has_meta" = 1 ]; then
|
|
815
|
+
if [ "$(meta_value "$meta" decisions_reviewed)" != 1 ] || [ "$previous" != "$keys" ]; then
|
|
816
|
+
printf 'decisions_reviewed=1\ndecision_keys=%s\n' "$keys" >> "$meta"
|
|
817
|
+
fi
|
|
818
|
+
fm_lock_release "$CAPTAIN_META_LOCK"
|
|
819
|
+
CAPTAIN_META_LOCK_HELD=0
|
|
820
|
+
|
|
821
|
+
# Transfer every still-open status decision to the durable captain-held
|
|
822
|
+
# inventory so the live status fold does not duplicate the same Captain's
|
|
823
|
+
# Call item. The transfer line is this home's own bookkeeping close,
|
|
824
|
+
# written by the turn that just reviewed the inventory, so it uses the
|
|
825
|
+
# guarded self-announced append (bin/fm-wake-lib.sh) and does not wake this
|
|
826
|
+
# same session; an append failure still fails this command loudly.
|
|
827
|
+
if [ -n "$keys" ]; then
|
|
828
|
+
while IFS=$'\t' read -r key _verb _summary; do
|
|
829
|
+
[ -n "$key" ] || continue
|
|
830
|
+
transfer_rc=0
|
|
831
|
+
fm_wake_status_append_self_announced "$STATE" "$status_file" \
|
|
832
|
+
"captain-held [key=$key]: tracked by $keys" || transfer_rc=$?
|
|
833
|
+
[ "$transfer_rc" -ne 2 ] || fail "cannot append the captain-held transfer for $origin/$key"
|
|
834
|
+
done <<EOF
|
|
835
|
+
$raw_open
|
|
836
|
+
EOF
|
|
837
|
+
fi
|
|
838
|
+
fi
|
|
839
|
+
printf 'complete: %s captain-call inventory reviewed%s\n' "$origin" "${keys:+ ($keys)}"
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
command_verify() {
|
|
843
|
+
local origin=${1:-} meta reviewed keys entry key open
|
|
844
|
+
[ "$#" -eq 1 ] || { usage >&2; exit 2; }
|
|
845
|
+
validate_slug origin-id "$origin"
|
|
846
|
+
meta="$STATE/$origin.meta"
|
|
847
|
+
[ -f "$meta" ] || fail "origin metadata is absent: $meta"
|
|
848
|
+
require_tasks_axi
|
|
849
|
+
reviewed=$(meta_value "$meta" decisions_reviewed)
|
|
850
|
+
[ "$reviewed" = 1 ] || fail "origin $origin has no completed captain-call inventory"
|
|
851
|
+
keys=$(meta_value "$meta" decision_keys)
|
|
852
|
+
if [ -n "$keys" ]; then
|
|
853
|
+
while IFS= read -r entry; do
|
|
854
|
+
[ -n "$entry" ] || continue
|
|
855
|
+
verify_hold_durable "$(resolve_entry "$origin" "$entry")"
|
|
856
|
+
done <<EOF
|
|
857
|
+
$(printf '%s\n' "$keys" | tr ',' '\n')
|
|
858
|
+
EOF
|
|
859
|
+
fi
|
|
860
|
+
open=$(origin_open_decisions "$origin")
|
|
861
|
+
while IFS=$'\t' read -r key _verb _summary; do
|
|
862
|
+
[ -n "$key" ] || continue
|
|
863
|
+
fail "open captain decision $origin/$key is not transferred to the captain-held inventory; re-run complete"
|
|
864
|
+
done <<EOF
|
|
865
|
+
$open
|
|
866
|
+
EOF
|
|
867
|
+
printf 'verified: %s captain-call inventory\n' "$origin"
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
# --- record divergence ------------------------------------------------------
|
|
871
|
+
#
|
|
872
|
+
# A captain call can be written down twice, and until now nothing said when
|
|
873
|
+
# those two records disagreed. A `resolved [key=...]` line closes the status-log
|
|
874
|
+
# fold outright; the structured captain-held task is closed by a SEPARATE act
|
|
875
|
+
# (`answer` above). Closing only on the status side therefore looks complete
|
|
876
|
+
# there while the durable record still says the captain owes an answer and
|
|
877
|
+
# keeps resurfacing it. The defect was never the separation; it was the silence.
|
|
878
|
+
#
|
|
879
|
+
# `diverged` is a read-only report of that contradiction and nothing else. It
|
|
880
|
+
# closes NOTHING. A captain call closed wrongly disappears without review, which
|
|
881
|
+
# is strictly worse than the noise this prints, so reconciling a divergence stays
|
|
882
|
+
# a human-owned act - and it runs in either direction: record what the captain
|
|
883
|
+
# actually said with `answer`, or re-open the status decision when that
|
|
884
|
+
# resolution was not the captain's word.
|
|
885
|
+
#
|
|
886
|
+
# What it flags, and only this: a task that is still open and still carries the
|
|
887
|
+
# captain-hold annotations, whose key was closed on the status side by the
|
|
888
|
+
# RESOLVE verb. The other closing verb is not a divergence: a `captain-held`
|
|
889
|
+
# close is the VERIFIED transfer to that very task, written by command_complete
|
|
890
|
+
# only after verifying it, so the structured row staying open behind it is the
|
|
891
|
+
# correct state. Neither is a still-open status decision - the OPEN DECISIONS
|
|
892
|
+
# fold already owns that one.
|
|
893
|
+
#
|
|
894
|
+
# Routed work is deliberately irrelevant. When the decision IS the deliverable
|
|
895
|
+
# there is nothing to route, so the test is only whether the status side already
|
|
896
|
+
# declared this task's key resolved.
|
|
897
|
+
# Nor does the report interpret why that resolution exists. A call can turn out
|
|
898
|
+
# not to be a captain arbitration at all - a premise can dissolve, or a question
|
|
899
|
+
# of fact can prove its first reading wrong - so the report says only that the
|
|
900
|
+
# two records disagree and names both reconciliation directions above.
|
|
901
|
+
#
|
|
902
|
+
# Cost stays flat on a healthy home: one `tasks-axi list`, one key scan per
|
|
903
|
+
# status log, and the precise per-key fold only for a key that already names a
|
|
904
|
+
# still-open task. If tasks-axi is unavailable or its listing cannot be parsed,
|
|
905
|
+
# the guard cannot read the structured record and prints nothing.
|
|
906
|
+
#
|
|
907
|
+
# Output: one `<task-id>\t<origin>\t<key>\t<title>` line per divergence, in
|
|
908
|
+
# status-log then key order; nothing when the two records agree.
|
|
909
|
+
|
|
910
|
+
# Every still-open task id in this home's backlog, one per line. Only the first
|
|
911
|
+
# two comma-separated listing fields are read - both are slugs that precede any
|
|
912
|
+
# quoted title - so a title containing commas or quotes cannot shift them.
|
|
913
|
+
open_task_ids() {
|
|
914
|
+
tasks_axi list 2>/dev/null | awk -F, '
|
|
915
|
+
/^ [A-Za-z0-9._-]+,/ {
|
|
916
|
+
id = $1
|
|
917
|
+
sub(/^ +/, "", id)
|
|
918
|
+
if ($2 != "done") print id
|
|
919
|
+
}
|
|
920
|
+
'
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
# Every key token stated anywhere in a status log. A cheap candidate scan: it
|
|
924
|
+
# over-includes tokens that are only prose, and status_key_closing_verb below is
|
|
925
|
+
# what actually decides what the stream says about a key.
|
|
926
|
+
status_log_key_tokens() { # <status-file>
|
|
927
|
+
grep -o '\[key=[A-Za-z0-9._-]*\]' "$1" 2>/dev/null |
|
|
928
|
+
sed 's/^\[key=//; s/\]$//' | LC_ALL=C sort -u
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
list_has_line() { # <newline-separated-list> <value>
|
|
932
|
+
case $'\n'"$1"$'\n' in
|
|
933
|
+
*$'\n'"$2"$'\n'*) return 0 ;;
|
|
934
|
+
*) return 1 ;;
|
|
935
|
+
esac
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
command_diverged() {
|
|
939
|
+
local ids resolve f origin tokens id keys key show title
|
|
940
|
+
[ "$#" -eq 0 ] || { usage >&2; exit 2; }
|
|
941
|
+
# Both records must belong to the SAME home or the comparison is meaningless:
|
|
942
|
+
# tasks-axi reads $FM_HOME's backlog, so a state dir pointed somewhere else
|
|
943
|
+
# would report one home's status logs against another home's tasks. Every
|
|
944
|
+
# production caller pairs the two; a mismatch stays silent rather than
|
|
945
|
+
# inventing a cross-home divergence.
|
|
946
|
+
[ "$STATE" = "$FM_HOME/state" ] || return 0
|
|
947
|
+
# A read-only listing on a per-wake path, so it skips the mutation-oriented
|
|
948
|
+
# compatibility floor and its extra probes: a listing this parser cannot read
|
|
949
|
+
# simply yields no candidates and the report stays silent.
|
|
950
|
+
command -v tasks-axi >/dev/null 2>&1 || return 0
|
|
951
|
+
ids=$(open_task_ids) || return 0
|
|
952
|
+
[ -n "$ids" ] || return 0
|
|
953
|
+
resolve=${FM_CLASSIFY_RESOLVE_VERB:-$FM_CLASSIFY_RESOLVE_VERB_DEFAULT}
|
|
954
|
+
for f in "$STATE"/*.status; do
|
|
955
|
+
[ -f "$f" ] && [ -r "$f" ] && [ ! -L "$f" ] || continue
|
|
956
|
+
origin=$(basename "$f"); origin=${origin%.status}
|
|
957
|
+
tokens=$(status_log_key_tokens "$f")
|
|
958
|
+
[ -n "$tokens" ] || continue
|
|
959
|
+
while IFS= read -r id; do
|
|
960
|
+
[ -n "$id" ] || continue
|
|
961
|
+
# The keys that could name this task in THIS log: the collapsed identity
|
|
962
|
+
# (the key IS the task id) and, for a pre-collapse row, the legacy derived
|
|
963
|
+
# one this origin would have minted.
|
|
964
|
+
keys=$id
|
|
965
|
+
case "$id" in
|
|
966
|
+
"$origin-decision-"?*) keys="$keys"$'\n'"${id#"$origin-decision-"}" ;;
|
|
967
|
+
esac
|
|
968
|
+
while IFS= read -r key; do
|
|
969
|
+
list_has_line "$tokens" "$key" || continue
|
|
970
|
+
[ "$(status_key_closing_verb "$f" "$key")" = "$resolve" ] || continue
|
|
971
|
+
show=$(task_show "$id") || continue
|
|
972
|
+
[ "$(show_field "$show" state)" != "done" ] || continue
|
|
973
|
+
[ "$(show_field_value "$show" hold_kind)" = captain ] || continue
|
|
974
|
+
# The title is the only free-text field here, and the report is
|
|
975
|
+
# TAB-separated, so it goes through the same sanitizer every other
|
|
976
|
+
# emitted field uses rather than being trusted to stay one clean line.
|
|
977
|
+
title=$(sanitize_field "$(show_field_value "$show" title)")
|
|
978
|
+
printf '%s\t%s\t%s\t%s\n' "$id" "$origin" "$key" "$title"
|
|
979
|
+
break
|
|
980
|
+
done <<INNER
|
|
981
|
+
$keys
|
|
982
|
+
INNER
|
|
983
|
+
done <<EOF
|
|
984
|
+
$ids
|
|
985
|
+
EOF
|
|
986
|
+
done
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
case "${1:-}" in
|
|
990
|
+
hold) shift; command_hold "$@" ;;
|
|
991
|
+
answer) shift; command_answer "$@" ;;
|
|
992
|
+
answers) shift; command_answers "$@" ;;
|
|
993
|
+
bind) shift; command_bind "$@" ;;
|
|
994
|
+
unbind) shift; command_unbind "$@" ;;
|
|
995
|
+
binding) shift; command_binding "$@" ;;
|
|
996
|
+
complete) shift; command_complete "$@" ;;
|
|
997
|
+
verify) shift; command_verify "$@" ;;
|
|
998
|
+
diverged) shift; command_diverged "$@" ;;
|
|
999
|
+
-h|--help) usage ;;
|
|
1000
|
+
*) usage >&2; exit 2 ;;
|
|
1001
|
+
esac
|