session-orchestrator 5.2.0 → 5.4.0
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/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +202 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* orphan-reaper.mjs — Teil B of Epic #1425: the NET under the process-group kill.
|
|
3
|
+
*
|
|
4
|
+
* Teil A (`process-group.mjs`) prevents orphans at the source. This module finds
|
|
5
|
+
* the ones that happen anyway — a session that crashed, a harness SIGKILL, a
|
|
6
|
+
* leak from a foreign repo on the same host — and reaps them.
|
|
7
|
+
*
|
|
8
|
+
* The 2026-09-20 incident this exists for: four orphaned `tsgo --noEmit`
|
|
9
|
+
* processes, two at PPID 1, 86-588% CPU and up to 8.0 GB RSS each; the Mac at
|
|
10
|
+
* 13% free memory, load 67.4. The existing detector
|
|
11
|
+
* (`countZombieProcesses`, `resource-probe/parsers.mjs`) would have counted ZERO:
|
|
12
|
+
* its `ps` carries no `ppid` and no `rss`, its name filter knows only
|
|
13
|
+
* `claude`/`node`, and it defines a zombie as IDLE (`cpu <= 1.0`) — the exact
|
|
14
|
+
* opposite of a runaway.
|
|
15
|
+
*
|
|
16
|
+
* ## Shape: pure decision, impure execution
|
|
17
|
+
*
|
|
18
|
+
* ps text → `parsePsSnapshot` → `decideReapCandidates` (PURE)
|
|
19
|
+
* → targeted ps + identity re-check BEFORE EVERY SIGNAL
|
|
20
|
+
* → `deps.killProcessGroup` → wait → re-measure (IMPURE)
|
|
21
|
+
*
|
|
22
|
+
* ## Layout (#1437)
|
|
23
|
+
*
|
|
24
|
+
* This file is the stable entry: the public re-exports below plus the CLI the
|
|
25
|
+
* trigger hooks spawn. The implementation lives in `orphan-reaper/`:
|
|
26
|
+
*
|
|
27
|
+
* defaults.mjs `REAPER_DEFAULTS` + the repo-path join (node:path only)
|
|
28
|
+
* scan-throttle.mjs B4 throttle marker — the trigger hooks import THIS
|
|
29
|
+
* ps-snapshot.mjs the binding `ps` format, its parser and runners
|
|
30
|
+
* reaper-decide.mjs the PURE candidate decision and its identity checks
|
|
31
|
+
* reaper-audit.mjs B5 audit record, writer, bounded reader, pruner, HR-101 rate
|
|
32
|
+
* scan.mjs `runOrphanScan` + its `resolveDeps` adapter
|
|
33
|
+
*
|
|
34
|
+
* Explicit named re-exports, never `export *`: a star barrel hides which module
|
|
35
|
+
* a name comes from, and the import census reads it as no reference at all.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
39
|
+
import { REAPER_DEFAULTS } from './orphan-reaper/defaults.mjs';
|
|
40
|
+
import { runOrphanScan } from './orphan-reaper/scan.mjs';
|
|
41
|
+
|
|
42
|
+
export { REAPER_DEFAULTS } from './orphan-reaper/defaults.mjs';
|
|
43
|
+
export {
|
|
44
|
+
SCAN_MARKER_RELPATH,
|
|
45
|
+
scanMarkerPath,
|
|
46
|
+
shouldScanNow,
|
|
47
|
+
touchScanMarker,
|
|
48
|
+
} from './orphan-reaper/scan-throttle.mjs';
|
|
49
|
+
export {
|
|
50
|
+
PS_ARGS,
|
|
51
|
+
parsePsSnapshot,
|
|
52
|
+
parsePsSnapshotDetailed,
|
|
53
|
+
psPidArgs,
|
|
54
|
+
} from './orphan-reaper/ps-snapshot.mjs';
|
|
55
|
+
export {
|
|
56
|
+
READ_ONLY_COMMAND_PATTERNS,
|
|
57
|
+
decideReapCandidates,
|
|
58
|
+
isReadOnlyCommand,
|
|
59
|
+
verifyGroupMemberIdentity,
|
|
60
|
+
} from './orphan-reaper/reaper-decide.mjs';
|
|
61
|
+
export {
|
|
62
|
+
FALSE_ALARM_SUSPECT_RATE,
|
|
63
|
+
REAPER_AUDIT_RELPATH,
|
|
64
|
+
auditPath,
|
|
65
|
+
falseAlarmRate,
|
|
66
|
+
} from './orphan-reaper/reaper-audit.mjs';
|
|
67
|
+
export { REAPER_SCAN_EVENT, resolveDeps, runOrphanScan } from './orphan-reaper/scan.mjs';
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// CLI (#1432 B4 follow-up)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
//
|
|
73
|
+
// The two trigger hooks (`hooks/on-stop.mjs`,
|
|
74
|
+
// `hooks/post-tool-batch-wave-signal.mjs`) spawn the scan as a DETACHED child.
|
|
75
|
+
// Before this tail existed they had to hand `node` an `--input-type=module -e
|
|
76
|
+
// <program>` string that dynamically imported this module — a program the
|
|
77
|
+
// hooks carried as source text, in two byte-identical copies. This entry point
|
|
78
|
+
// replaces it with a plain argv call, so the hooks spawn `node
|
|
79
|
+
// scripts/lib/orphan-reaper.mjs --repo-root <p> --mode <m> …` and the child's
|
|
80
|
+
// contract lives HERE, in one place, next to the function it drives.
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse the CLI argv — PURE, and exported so the flag contract is testable
|
|
84
|
+
* without starting a process (the argv form is what the two hooks build, so a
|
|
85
|
+
* typo in it fails silently as a dead detached child).
|
|
86
|
+
*
|
|
87
|
+
* Never throws: an unknown flag, a missing value and a non-numeric value all
|
|
88
|
+
* land in `errors`, which the tail maps to exit 2. Returning the errors rather
|
|
89
|
+
* than throwing keeps this usable from a test and from the tail alike.
|
|
90
|
+
*
|
|
91
|
+
* @param {string[]} [argv] `process.argv.slice(2)`
|
|
92
|
+
* @returns {{help: boolean, json: boolean, repoRoot: string|null, mode: 'report'|'kill',
|
|
93
|
+
* dryRun: boolean, minAgeSeconds: number, killGraceMs: number, verifyWaitMs: number,
|
|
94
|
+
* falseAlarmWindow: number, errors: string[]}}
|
|
95
|
+
*/
|
|
96
|
+
export function parseReaperCliArgs(argv = []) {
|
|
97
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
98
|
+
/** @type {string[]} */
|
|
99
|
+
const errors = [];
|
|
100
|
+
const out = {
|
|
101
|
+
help: false,
|
|
102
|
+
json: false,
|
|
103
|
+
repoRoot: null,
|
|
104
|
+
mode: /** @type {'report'|'kill'} */ ('report'),
|
|
105
|
+
dryRun: true,
|
|
106
|
+
minAgeSeconds: REAPER_DEFAULTS.minAgeSeconds,
|
|
107
|
+
killGraceMs: REAPER_DEFAULTS.killGraceMs,
|
|
108
|
+
verifyWaitMs: REAPER_DEFAULTS.verifyWaitMs,
|
|
109
|
+
falseAlarmWindow: REAPER_DEFAULTS.falseAlarmWindow,
|
|
110
|
+
errors,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Consume the value of `--flag <value>`; records an error when it is absent.
|
|
115
|
+
* @param {string} flag @param {number} i @returns {string|null}
|
|
116
|
+
*/
|
|
117
|
+
const valueAt = (flag, i) => {
|
|
118
|
+
const value = args[i + 1];
|
|
119
|
+
if (value === undefined || value.startsWith('--')) {
|
|
120
|
+
errors.push(`missing value for ${flag}`);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A count of seconds/milliseconds: finite and non-negative. A `NaN` here
|
|
128
|
+
* would reach `runOrphanScan` as a threshold that compares false against
|
|
129
|
+
* everything — a silently disarmed gate, which is why it is an error and
|
|
130
|
+
* never a fallback to the default.
|
|
131
|
+
* @param {string} flag @param {string} raw @returns {number|null}
|
|
132
|
+
*/
|
|
133
|
+
const numberFrom = (flag, raw) => {
|
|
134
|
+
const n = Number(raw);
|
|
135
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
136
|
+
errors.push(`${flag} expects a non-negative number, got ${JSON.stringify(raw)}`);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return n;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
143
|
+
const arg = args[i];
|
|
144
|
+
switch (arg) {
|
|
145
|
+
case '--help':
|
|
146
|
+
case '-h':
|
|
147
|
+
out.help = true;
|
|
148
|
+
break;
|
|
149
|
+
case '--json':
|
|
150
|
+
out.json = true;
|
|
151
|
+
break;
|
|
152
|
+
case '--repo-root': {
|
|
153
|
+
const value = valueAt(arg, i);
|
|
154
|
+
if (value !== null) { out.repoRoot = value; i += 1; }
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case '--mode': {
|
|
158
|
+
const value = valueAt(arg, i);
|
|
159
|
+
if (value === null) break;
|
|
160
|
+
i += 1;
|
|
161
|
+
if (value !== 'report' && value !== 'kill') {
|
|
162
|
+
errors.push(`--mode expects report|kill, got ${JSON.stringify(value)}`);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
out.mode = value;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case '--min-age-seconds':
|
|
169
|
+
case '--kill-grace-ms':
|
|
170
|
+
case '--verify-wait-ms': {
|
|
171
|
+
const value = valueAt(arg, i);
|
|
172
|
+
if (value === null) break;
|
|
173
|
+
i += 1;
|
|
174
|
+
const n = numberFrom(arg, value);
|
|
175
|
+
if (n === null) break;
|
|
176
|
+
if (arg === '--min-age-seconds') out.minAgeSeconds = n;
|
|
177
|
+
else if (arg === '--kill-grace-ms') out.killGraceMs = n;
|
|
178
|
+
else out.verifyWaitMs = n;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case '--false-alarm-window': {
|
|
182
|
+
const value = valueAt(arg, i);
|
|
183
|
+
if (value === null) break;
|
|
184
|
+
i += 1;
|
|
185
|
+
const n = numberFrom(arg, value);
|
|
186
|
+
if (n === null) break;
|
|
187
|
+
// A window of 0 is not "no window" — `falseAlarmRate` reads it as "every
|
|
188
|
+
// firing ever recorded", which is the opposite of the rolling window the
|
|
189
|
+
// key names. The config parser clamps at 1; the CLI refuses instead, so
|
|
190
|
+
// a hand-run diagnosis never measures a different population than the
|
|
191
|
+
// hook does.
|
|
192
|
+
if (n < 1) {
|
|
193
|
+
errors.push(`${arg} expects a number >= 1, got ${JSON.stringify(value)}`);
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
out.falseAlarmWindow = n;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
errors.push(`unknown argument: ${arg}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// `kill` is the ONLY value that disarms the dry run — same direction as
|
|
205
|
+
// `runOrphanScan`'s own `dryRun = true` default: a caller that mistypes the
|
|
206
|
+
// mode scans and reports, it does not signal. (A rejected `--mode` value
|
|
207
|
+
// already left `mode` at `report`, so this stays `true` there too.)
|
|
208
|
+
out.dryRun = out.mode !== 'kill';
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** `--help` text. Exit codes documented here are the ones the tail returns. */
|
|
213
|
+
const REAPER_HELP_TEXT = `orphan-reaper.mjs — scan for orphaned read-only gate processes (#1425 Teil B)
|
|
214
|
+
|
|
215
|
+
USAGE
|
|
216
|
+
node scripts/lib/orphan-reaper.mjs [options]
|
|
217
|
+
|
|
218
|
+
DESCRIPTION
|
|
219
|
+
Runs ONE orphan scan: ps -> decide -> (with --mode kill) re-verify identity
|
|
220
|
+
against a fresh snapshot -> kill the process group -> audit. Candidates are a
|
|
221
|
+
CONJUNCTION: recorded in this repo's gate-process ledger, PPID 1, older than
|
|
222
|
+
--min-age-seconds, a read-only gate command, and an identity that still
|
|
223
|
+
verifies. Spawned detached by hooks/on-stop.mjs and
|
|
224
|
+
hooks/post-tool-batch-wave-signal.mjs; runnable by hand for diagnosis.
|
|
225
|
+
|
|
226
|
+
OPTIONS
|
|
227
|
+
--repo-root <path> Repo whose ledger, audit and marker are used
|
|
228
|
+
(default: cwd).
|
|
229
|
+
--mode report|kill report = dry run, signals nothing (default);
|
|
230
|
+
kill = send the SIGTERM/SIGKILL ladder.
|
|
231
|
+
--min-age-seconds <n> Minimum elapsed time before a process is reapable
|
|
232
|
+
(default: ${REAPER_DEFAULTS.minAgeSeconds}).
|
|
233
|
+
--kill-grace-ms <n> Grace between SIGTERM and SIGKILL
|
|
234
|
+
(default: ${REAPER_DEFAULTS.killGraceMs}).
|
|
235
|
+
--verify-wait-ms <n> Wait before re-measuring the effect
|
|
236
|
+
(default: ${REAPER_DEFAULTS.verifyWaitMs}).
|
|
237
|
+
--false-alarm-window <n> Rolling window of audit DECISIONS the HR-101
|
|
238
|
+
false-alarm rate is judged over; >= 1
|
|
239
|
+
(default: ${REAPER_DEFAULTS.falseAlarmWindow}).
|
|
240
|
+
--json Emit the full scan result as one JSON object.
|
|
241
|
+
--help, -h Show this help and exit.
|
|
242
|
+
|
|
243
|
+
EXIT CODES
|
|
244
|
+
0 the scan ran (0 candidates is a normal, successful scan)
|
|
245
|
+
2 usage error, or a degraded scan that measured nothing (\`skipped\`)
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* CLI body: one scan, one line (or one JSON object) of output.
|
|
250
|
+
*
|
|
251
|
+
* A degraded scan — `skipped: 'ps-failed' | 'ledger-unreadable' | …` — exits 2,
|
|
252
|
+
* not 0: it measured NOTHING, and an exit 0 there is exactly the "missing
|
|
253
|
+
* measurement looks like a zero" shape this module refuses everywhere else.
|
|
254
|
+
* The detached hook child ignores the code; a human or a CI caller does not.
|
|
255
|
+
*
|
|
256
|
+
* @param {string[]} argv `process.argv.slice(2)`
|
|
257
|
+
* @returns {Promise<number>} process exit code
|
|
258
|
+
*/
|
|
259
|
+
async function mainCli(argv) {
|
|
260
|
+
const cli = parseReaperCliArgs(argv);
|
|
261
|
+
|
|
262
|
+
if (cli.help) {
|
|
263
|
+
process.stdout.write(REAPER_HELP_TEXT);
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
if (cli.errors.length > 0) {
|
|
267
|
+
for (const message of cli.errors) process.stderr.write(`orphan-reaper: ${message}\n`);
|
|
268
|
+
process.stderr.write('Run with --help for usage.\n');
|
|
269
|
+
return 2;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const repoRoot = cli.repoRoot ?? process.cwd();
|
|
273
|
+
let result;
|
|
274
|
+
try {
|
|
275
|
+
result = await runOrphanScan({
|
|
276
|
+
repoRoot,
|
|
277
|
+
dryRun: cli.dryRun,
|
|
278
|
+
minAgeSeconds: cli.minAgeSeconds,
|
|
279
|
+
killGraceMs: cli.killGraceMs,
|
|
280
|
+
verifyWaitMs: cli.verifyWaitMs,
|
|
281
|
+
falseAlarmWindow: cli.falseAlarmWindow,
|
|
282
|
+
});
|
|
283
|
+
} catch (err) {
|
|
284
|
+
// runOrphanScan carries a NO-THROW contract; this catch exists so a broken
|
|
285
|
+
// contract surfaces as a tool error instead of an unhandled rejection.
|
|
286
|
+
process.stderr.write(`orphan-reaper: scan failed — ${err?.message ?? String(err)}\n`);
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (cli.json) {
|
|
291
|
+
process.stdout.write(`${JSON.stringify({ repoRoot, mode: cli.mode, ...result })}\n`);
|
|
292
|
+
} else {
|
|
293
|
+
process.stdout.write(
|
|
294
|
+
`orphan-reaper: scanned=${result.scanned} candidates=${result.candidates.length} `
|
|
295
|
+
+ `reported=${result.reported.length} unattributed=${result.unattributed} `
|
|
296
|
+
+ `peer_liveness=${result.peerLiveness ?? 'none'} killed=${result.killed.length} `
|
|
297
|
+
+ `malformed=${result.malformed} mode=${cli.mode}`
|
|
298
|
+
+ `${result.skipped ? ` skipped=${result.skipped}` : ''}\n`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return result.skipped ? 2 : 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Entry guard: a bare `import()` of this module must do NOTHING — tests and
|
|
305
|
+
// any caller of the re-exports above import it. Written
|
|
306
|
+
// in the one symlink-safe form `scripts/lib/validate/check-entry-guard.mjs`
|
|
307
|
+
// and `check-hook-entry-guards.mjs` accept.
|
|
308
|
+
//
|
|
309
|
+
// `process.exitCode` rather than `process.exit()`: `--json` carries the whole
|
|
310
|
+
// result, whose `rejected` array materialises every PPID-1 row not in the
|
|
311
|
+
// ledger — measured 2026-09-22 on this host, 40.224 bytes for 834 processes,
|
|
312
|
+
// the same order as the 64 KiB pipe buffer above which `process.exit()`
|
|
313
|
+
// DISCARDS the pending write and turns a full result into a truncated one.
|
|
314
|
+
if (isMainModule(import.meta.url)) {
|
|
315
|
+
process.exitCode = await mainCli(process.argv.slice(2));
|
|
316
|
+
}
|
|
@@ -36,6 +36,16 @@
|
|
|
36
36
|
* dispatcher: (optional; host-local cross-repo dispatcher autonomy override — #679)
|
|
37
37
|
* autonomy: string ('' = no override; resolver enum off | advisory | autonomous-gated;
|
|
38
38
|
* precedence env SO_DISPATCHER_AUTONOMY > this > committed > off)
|
|
39
|
+
* vault-integration: (optional; host-local switch for the vault-integration gate — SO#1448)
|
|
40
|
+
* enabled: boolean (false = off on this host; true is ignored — the host may not enable)
|
|
41
|
+
* mode: 'off' | 'warn' | 'strict' (applied only when weaker than the committed mode;
|
|
42
|
+
* precedence env SO_VAULT_INTEGRATION > this > committed.
|
|
43
|
+
* An invalid value drops ONLY this section, see loadOwnerConfig)
|
|
44
|
+
* vault-dirs: (optional list; per-directory vault-dir — agents/vault#319)
|
|
45
|
+
* - path: string (the vault directory)
|
|
46
|
+
* match: { path-prefix: string } (required; cwd directory prefix, first match wins)
|
|
47
|
+
* name: string (optional label, defaults to path)
|
|
48
|
+
* precedence env SO_VAULT_DIR > this > paths.vault-dir > committed
|
|
39
49
|
*
|
|
40
50
|
* ── Exports ───────────────────────────────────────────────────────────────────
|
|
41
51
|
*
|
|
@@ -44,7 +54,8 @@
|
|
|
44
54
|
* validateOwnerSections(obj) — pure validation, no I/O; bucketed per section (#820)
|
|
45
55
|
* validateOwnerConfig(obj) — pure validation, no I/O; thin wrapper over validateOwnerSections
|
|
46
56
|
* loadOwnerConfig({path?}) — reads file; per-section tolerance for OPTIONAL
|
|
47
|
-
* sections (paths/dispatcher/vaults/
|
|
57
|
+
* sections (paths/dispatcher/vault-integration/vaults/
|
|
58
|
+
* baselines/vault-dirs) — #820
|
|
48
59
|
* writeOwnerConfig(config, {path?}) — validates, writes YAML, creates dir
|
|
49
60
|
* getDefaults() — returns sensible default config object
|
|
50
61
|
*
|
|
@@ -202,9 +213,12 @@ const REQUIRED_SECTIONS = /** @type {const} */ (['owner', 'tone', 'efficiency',
|
|
|
202
213
|
* sections — currently `scripts/lib/owner-config-banner.mjs`, which derived the
|
|
203
214
|
* #1244 "survived a whole-file discard" set from a hand-mirrored local copy.
|
|
204
215
|
* Frozen so a consumer cannot mutate the shared array.
|
|
216
|
+
*
|
|
217
|
+
* `vault-integration` has no `getDefaults()` entry: absent means "no host
|
|
218
|
+
* override", so a dropped section simply leaves the committed gate in force.
|
|
205
219
|
*/
|
|
206
220
|
export const OPTIONAL_OBJECT_SECTIONS = /** @type {const} */ (
|
|
207
|
-
Object.freeze(['paths', 'dispatcher'])
|
|
221
|
+
Object.freeze(['paths', 'dispatcher', 'vault-integration'])
|
|
208
222
|
);
|
|
209
223
|
|
|
210
224
|
/**
|
|
@@ -213,7 +227,10 @@ export const OPTIONAL_OBJECT_SECTIONS = /** @type {const} */ (
|
|
|
213
227
|
* `parseNamedVaults`/`parseBaselines` — that already drops bad entries with
|
|
214
228
|
* its own WARN). Surfaced here only via `sectionWarnings`, never dropped.
|
|
215
229
|
*/
|
|
216
|
-
const OPTIONAL_LIST_SECTIONS = /** @type {const} */ (['vaults', 'baselines']);
|
|
230
|
+
const OPTIONAL_LIST_SECTIONS = /** @type {const} */ (['vaults', 'baselines', 'vault-dirs']);
|
|
231
|
+
|
|
232
|
+
/** Host-settable vault-integration modes (mirrors VAULT_MODE_VALUES in config-schema.mjs). */
|
|
233
|
+
const VALID_VAULT_INTEGRATION_MODES = ['off', 'warn', 'strict'];
|
|
217
234
|
|
|
218
235
|
/** Canonical validation-order — MUST match the pre-#820 inline validation order. */
|
|
219
236
|
const SECTION_ORDER = /** @type {const} */ ([
|
|
@@ -421,6 +438,35 @@ export function validateOwnerSections(obj) {
|
|
|
421
438
|
sections.dispatcher = { valid: errors.length === 0, errors };
|
|
422
439
|
}
|
|
423
440
|
|
|
441
|
+
// ── vault-integration (optional; host-local gate switch — SO#1448) ──────────
|
|
442
|
+
// STRICT enum validation, unlike `dispatcher`: a typo like `mode: of` must not
|
|
443
|
+
// fall through to the committed `strict` silently while the operator believes
|
|
444
|
+
// the host is off. An invalid section is dropped (and reported) on its own —
|
|
445
|
+
// `paths` and every other section survive (#820 per-section tolerance).
|
|
446
|
+
{
|
|
447
|
+
const errors = [];
|
|
448
|
+
const vi = obj['vault-integration'];
|
|
449
|
+
if (vi !== undefined && vi !== null) {
|
|
450
|
+
if (!isPlainObject(vi)) {
|
|
451
|
+
errors.push('vault-integration must be an object when present');
|
|
452
|
+
} else {
|
|
453
|
+
if (vi.enabled !== undefined && vi.enabled !== null && typeof vi.enabled !== 'boolean') {
|
|
454
|
+
errors.push(`vault-integration.enabled must be a boolean, got: ${typeof vi.enabled}`);
|
|
455
|
+
}
|
|
456
|
+
if (
|
|
457
|
+
vi.mode !== undefined &&
|
|
458
|
+
vi.mode !== null &&
|
|
459
|
+
!VALID_VAULT_INTEGRATION_MODES.includes(vi.mode)
|
|
460
|
+
) {
|
|
461
|
+
errors.push(
|
|
462
|
+
`vault-integration.mode must be one of ${VALID_VAULT_INTEGRATION_MODES.join(', ')}, got: ${JSON.stringify(vi.mode)}`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
sections['vault-integration'] = { valid: errors.length === 0, errors };
|
|
468
|
+
}
|
|
469
|
+
|
|
424
470
|
// ── vaults (optional; N named vaults for walk-up resolution — #700) ──────────
|
|
425
471
|
// Drop-and-WARN on malformed entries is handled by parseNamedVaults() in
|
|
426
472
|
// named-vault-resolver.mjs. Here we only validate the container shape to
|
|
@@ -512,6 +558,47 @@ export function validateOwnerSections(obj) {
|
|
|
512
558
|
sections.baselines = { valid: errors.length === 0, errors };
|
|
513
559
|
}
|
|
514
560
|
|
|
561
|
+
// ── vault-dirs (optional; per-directory vault-dir — agents/vault#319) ────────
|
|
562
|
+
// Same entry shape as baselines: (and parsed by the same parseBaselines() with
|
|
563
|
+
// section 'vault-dirs'), except `name` is optional. List section: surfaced via
|
|
564
|
+
// sectionWarnings, never dropped — the resolver drops bad entries itself.
|
|
565
|
+
{
|
|
566
|
+
const errors = [];
|
|
567
|
+
const vaultDirs = obj['vault-dirs'];
|
|
568
|
+
if (vaultDirs !== undefined && vaultDirs !== null) {
|
|
569
|
+
if (!Array.isArray(vaultDirs)) {
|
|
570
|
+
errors.push('vault-dirs must be an array when present');
|
|
571
|
+
} else {
|
|
572
|
+
for (let i = 0; i < vaultDirs.length; i++) {
|
|
573
|
+
const entry = vaultDirs[i];
|
|
574
|
+
if (!isPlainObject(entry)) {
|
|
575
|
+
errors.push(`vault-dirs[${i}] must be an object`);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (typeof entry.path !== 'string' || entry.path.trim() === '') {
|
|
579
|
+
errors.push(`vault-dirs[${i}].path must be a non-empty string`);
|
|
580
|
+
}
|
|
581
|
+
if (
|
|
582
|
+
entry.name !== undefined &&
|
|
583
|
+
entry.name !== null &&
|
|
584
|
+
(typeof entry.name !== 'string' || entry.name.trim() === '')
|
|
585
|
+
) {
|
|
586
|
+
errors.push(`vault-dirs[${i}].name must be a non-empty string when present`);
|
|
587
|
+
}
|
|
588
|
+
if (!isPlainObject(entry.match)) {
|
|
589
|
+
errors.push(`vault-dirs[${i}].match must be an object`);
|
|
590
|
+
} else if (
|
|
591
|
+
typeof entry.match['path-prefix'] !== 'string' ||
|
|
592
|
+
entry.match['path-prefix'].trim() === ''
|
|
593
|
+
) {
|
|
594
|
+
errors.push(`vault-dirs[${i}].match.path-prefix must be a non-empty string`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
sections['vault-dirs'] = { valid: errors.length === 0, errors };
|
|
600
|
+
}
|
|
601
|
+
|
|
515
602
|
const errors = SECTION_ORDER.flatMap((name) => sections[name].errors);
|
|
516
603
|
return { sections, errors };
|
|
517
604
|
}
|
|
@@ -540,10 +627,10 @@ export function validateOwnerConfig(obj) {
|
|
|
540
627
|
* Per-section tolerance (#820): an invalid REQUIRED section (owner, tone,
|
|
541
628
|
* efficiency, hardware-sharing) still discards the whole file (legacy
|
|
542
629
|
* behaviour, unchanged). An invalid OPTIONAL object section (paths,
|
|
543
|
-
* dispatcher) is instead replaced by its default value — the rest of the
|
|
630
|
+
* dispatcher, vault-integration) is instead replaced by its default value — the rest of the
|
|
544
631
|
* file survives, `source` becomes `'partial'`, and the drop is reported via
|
|
545
632
|
* `droppedSections` + a stderr WARN. OPTIONAL list sections (vaults,
|
|
546
|
-
* baselines) are passed through UNTOUCHED even when strict-invalid — their
|
|
633
|
+
* baselines, vault-dirs) are passed through UNTOUCHED even when strict-invalid — their
|
|
547
634
|
* consumers already run a lenient parse-at-point-of-use pass — and are
|
|
548
635
|
* surfaced only via `sectionWarnings` (never dropped, never counted towards
|
|
549
636
|
* `'partial'`).
|
|
@@ -658,6 +745,14 @@ export function loadOwnerConfig(opts = {}) {
|
|
|
658
745
|
discardDropped.push({ section: name, errors: sec.errors });
|
|
659
746
|
}
|
|
660
747
|
}
|
|
748
|
+
// `vault-dirs:` (agents/vault#319) survives the discard too, raw — exactly as
|
|
749
|
+
// on the required-valid branch below; parseBaselines() drops bad entries at
|
|
750
|
+
// point-of-use. Dropping it would hand a repo under a declared prefix the
|
|
751
|
+
// host-wide `paths.vault-dir` (which DOES survive above): the silent vault
|
|
752
|
+
// override #319 exists to prevent, triggered by an unrelated `tone:` typo.
|
|
753
|
+
if (parsed['vault-dirs'] !== undefined && parsed['vault-dirs'] !== null) {
|
|
754
|
+
discardConfig['vault-dirs'] = parsed['vault-dirs'];
|
|
755
|
+
}
|
|
661
756
|
const discardResult = { config: discardConfig, source: 'defaults', errors: allErrors };
|
|
662
757
|
if (discardDropped.length > 0) discardResult.droppedSections = discardDropped;
|
|
663
758
|
return discardResult;
|
|
@@ -673,7 +768,10 @@ export function loadOwnerConfig(opts = {}) {
|
|
|
673
768
|
for (const name of OPTIONAL_OBJECT_SECTIONS) {
|
|
674
769
|
const sec = sections[name];
|
|
675
770
|
if (sec && !sec.valid) {
|
|
676
|
-
|
|
771
|
+
// A section without a default (vault-integration) is removed, not set to
|
|
772
|
+
// `undefined`: js-yaml's dump rejects undefined values on a write-back.
|
|
773
|
+
if (defaults[name] === undefined) delete config[name];
|
|
774
|
+
else config[name] = defaults[name];
|
|
677
775
|
droppedSections.push({ section: name, errors: sec.errors });
|
|
678
776
|
const firstError = sec.errors[0] ?? 'invalid section';
|
|
679
777
|
console.warn(
|
|
@@ -49,7 +49,7 @@ const SENTINEL_BEGIN_RE = new RegExp(`<!--\\s*BEGIN\\s+MANAGED:\\s*(${SECTION_NA
|
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
51
|
* @typedef {Object} Conflict
|
|
52
|
-
* @property {'duplicate-section' | 'orphan-begin'} type
|
|
52
|
+
* @property {'duplicate-section' | 'orphan-begin' | 'unmapped-preamble' | 'multi-heading-region'} type
|
|
53
53
|
* @property {string} [name] — section name (for duplicate-section and orphan-begin)
|
|
54
54
|
*/
|
|
55
55
|
|
|
@@ -317,8 +317,14 @@ function escapeRegex(s) {
|
|
|
317
317
|
// the two, so `/evolve dialectic --apply` could not complete (#1310, correcting
|
|
318
318
|
// #1303 point 3 — the signatures line up, the seam did not).
|
|
319
319
|
//
|
|
320
|
-
// The mapping is DERIVED, not invented:
|
|
321
|
-
//
|
|
320
|
+
// The mapping is DERIVED, not invented: a managed region wrapping exactly one
|
|
321
|
+
// `## ` heading makes that heading the section unit. A region wrapping SEVERAL
|
|
322
|
+
// headings cannot be addressed per heading without splitting the region (a
|
|
323
|
+
// storage-format change), so it is fail-closed (#1380): `deriveManagedUpdates`
|
|
324
|
+
// never targets it, and every proposed heading that lives inside it is skipped
|
|
325
|
+
// — neither replaced (which would delete its sibling headings' hand text) nor
|
|
326
|
+
// appended (which would duplicate the heading) — and reported as a
|
|
327
|
+
// `multi-heading-region` conflict by `mergeDerivedBody`. Existing
|
|
322
328
|
// names are read back out of the card rather than re-slugified, because the live
|
|
323
329
|
// names are NOT a pure function of their headings — measured 2026-09-11 in
|
|
324
330
|
// `.orchestrator/peers/AGENT.md`: "Guard and protocol-migration discipline" →
|
|
@@ -359,6 +365,9 @@ function slugifyHeading(heading) {
|
|
|
359
365
|
* • Text BEFORE the first `## ` heading → returned as `preamble`. It is NOT
|
|
360
366
|
* written into any section (it has no sentinel to own it); `mergeDerivedBody`
|
|
361
367
|
* surfaces it as an `unmapped-preamble` conflict so a caller cannot miss it.
|
|
368
|
+
* • Heading that lives inside an existing managed region wrapping MORE than one
|
|
369
|
+
* `## ` heading → SKIPPED (not in `managedUpdates`, not in `mapping`) and
|
|
370
|
+
* listed in `skippedRegions`; that region is never a target (#1380).
|
|
362
371
|
* • Content under `###`+ headings stays inside its parent `##` section.
|
|
363
372
|
* • A proposed body that already carries BEGIN/END sentinels keeps them inside
|
|
364
373
|
* the section content — it is the deriver's job not to emit them (see
|
|
@@ -370,7 +379,8 @@ function slugifyHeading(heading) {
|
|
|
370
379
|
* @param {string} [existingBody] — the on-disk body, for existing-name lookup
|
|
371
380
|
* @returns {{ managedUpdates: Record<string,string>,
|
|
372
381
|
* mapping: Array<{heading: string, section: string, origin: 'existing'|'new'}>,
|
|
373
|
-
* preamble: string
|
|
382
|
+
* preamble: string,
|
|
383
|
+
* skippedRegions: Array<{region: string, headings: string[], skipped: string[]}> }}
|
|
374
384
|
*/
|
|
375
385
|
export function deriveManagedUpdates(proposedBody, existingBody = '') {
|
|
376
386
|
if (typeof proposedBody !== 'string') {
|
|
@@ -383,13 +393,25 @@ export function deriveManagedUpdates(proposedBody, existingBody = '') {
|
|
|
383
393
|
// heading-key → existing section name, read out of the live card
|
|
384
394
|
const existingByHeading = new Map();
|
|
385
395
|
const existingNames = new Set();
|
|
396
|
+
// Fail-closed bookkeeping for regions wrapping more than one `## ` heading (#1380).
|
|
397
|
+
/** @type {Map<string, {region: string, headings: string[], skipped: string[]}>} */
|
|
398
|
+
const multiHeading = new Map();
|
|
399
|
+
const multiHeadingByKey = new Map();
|
|
386
400
|
if (existingBody.length > 0) {
|
|
387
401
|
for (const s of parseSections(existingBody).sections) {
|
|
388
402
|
if (s.type !== 'managed') continue;
|
|
389
403
|
existingNames.add(s.name);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
404
|
+
H2_RE.lastIndex = 0;
|
|
405
|
+
const regionHeads = [...s.content.matchAll(H2_RE)].map(h => h[1]);
|
|
406
|
+
if (regionHeads.length > 1) {
|
|
407
|
+
const entry = multiHeading.get(s.name) ?? { region: s.name, headings: [], skipped: [] };
|
|
408
|
+
entry.headings.push(...regionHeads);
|
|
409
|
+
multiHeading.set(s.name, entry);
|
|
410
|
+
for (const h of regionHeads) multiHeadingByKey.set(headingKey(h), entry);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (regionHeads.length === 1 && !existingByHeading.has(headingKey(regionHeads[0]))) {
|
|
414
|
+
existingByHeading.set(headingKey(regionHeads[0]), s.name);
|
|
393
415
|
}
|
|
394
416
|
}
|
|
395
417
|
}
|
|
@@ -409,6 +431,12 @@ export function deriveManagedUpdates(proposedBody, existingBody = '') {
|
|
|
409
431
|
const end = i + 1 < heads.length ? heads[i + 1].index : proposedBody.length;
|
|
410
432
|
const content = proposedBody.slice(start, end).trim();
|
|
411
433
|
|
|
434
|
+
const blocked = multiHeadingByKey.get(headingKey(heading));
|
|
435
|
+
if (blocked !== undefined) {
|
|
436
|
+
blocked.skipped.push(heading);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
412
440
|
const existing = existingByHeading.get(headingKey(heading));
|
|
413
441
|
let section;
|
|
414
442
|
let origin;
|
|
@@ -419,7 +447,9 @@ export function deriveManagedUpdates(proposedBody, existingBody = '') {
|
|
|
419
447
|
const base = slugifyHeading(heading);
|
|
420
448
|
let candidate = base;
|
|
421
449
|
let n = 2;
|
|
422
|
-
|
|
450
|
+
// A multi-heading region is never a target — not even via a slug that
|
|
451
|
+
// happens to equal its name.
|
|
452
|
+
while (used.has(candidate) || multiHeading.has(candidate)) {
|
|
423
453
|
candidate = `${base}-${n++}`;
|
|
424
454
|
}
|
|
425
455
|
section = candidate;
|
|
@@ -431,7 +461,8 @@ export function deriveManagedUpdates(proposedBody, existingBody = '') {
|
|
|
431
461
|
mapping.push({ heading, section, origin });
|
|
432
462
|
}
|
|
433
463
|
|
|
434
|
-
|
|
464
|
+
const skippedRegions = [...multiHeading.values()].filter(e => e.skipped.length > 0);
|
|
465
|
+
return { managedUpdates, mapping, preamble, skippedRegions };
|
|
435
466
|
}
|
|
436
467
|
|
|
437
468
|
/**
|
|
@@ -439,13 +470,20 @@ export function deriveManagedUpdates(proposedBody, existingBody = '') {
|
|
|
439
470
|
* This is the function `/evolve dialectic --apply` calls; `mergePeerCard` stays
|
|
440
471
|
* the section-map primitive its existing callers already use.
|
|
441
472
|
*
|
|
473
|
+
* A managed region wrapping several `## ` headings is left byte-unchanged; each
|
|
474
|
+
* skipped proposed heading surfaces as a
|
|
475
|
+
* `{ type: 'multi-heading-region', region, headings, skipped }` conflict (#1380).
|
|
476
|
+
*
|
|
442
477
|
* @param {string} existingBody
|
|
443
478
|
* @param {string} proposedBody
|
|
444
479
|
* @returns {MergeResult & { mapping: Array<{heading: string, section: string, origin: 'existing'|'new'}>, preamble: string }}
|
|
445
480
|
*/
|
|
446
481
|
export function mergeDerivedBody(existingBody, proposedBody) {
|
|
447
|
-
const { managedUpdates, mapping, preamble } = deriveManagedUpdates(proposedBody, existingBody);
|
|
482
|
+
const { managedUpdates, mapping, preamble, skippedRegions } = deriveManagedUpdates(proposedBody, existingBody);
|
|
448
483
|
const result = mergePeerCard(existingBody, managedUpdates);
|
|
484
|
+
for (const { region, headings, skipped } of skippedRegions) {
|
|
485
|
+
result.conflicts.push({ type: 'multi-heading-region', region, headings, skipped });
|
|
486
|
+
}
|
|
449
487
|
if (preamble.length > 0) {
|
|
450
488
|
result.conflicts.push({ type: 'unmapped-preamble', content: preamble });
|
|
451
489
|
}
|