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
|
@@ -1812,6 +1812,32 @@ export function redirectRuleMatches(rule, command, opts = {}) {
|
|
|
1812
1812
|
// an existing repo-relative rule, and a repo-relative entry still cannot
|
|
1813
1813
|
// escape the root. An unexpandable `~` (no usable `home`) matches NOTHING —
|
|
1814
1814
|
// fail-closed for the new class, since an unexpanded `~` names no file.
|
|
1815
|
+
const compiled = compileTargetDenylist(denylist, home);
|
|
1816
|
+
if (compiled.relRegexes.length === 0 && compiled.absRegexes.length === 0) return false;
|
|
1817
|
+
|
|
1818
|
+
for (const entry of extractRedirectTargets(command)) {
|
|
1819
|
+
if (entry.unresolved) continue;
|
|
1820
|
+
if (!modes.has(entry.mode)) continue;
|
|
1821
|
+
if (targetMatchesDenylist(entry.target, compiled, repoRoot, home)) return true;
|
|
1822
|
+
}
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/**
|
|
1827
|
+
* Compile a rule's `target-denylist` into the two glob classes described in
|
|
1828
|
+
* `redirectRuleMatches` (#1362): repo-relative entries and home-anchored (`~/…`)
|
|
1829
|
+
* entries. Extracted so a SECOND rule class can reuse the exact same compilation
|
|
1830
|
+
* instead of growing a third copy of the glob logic (#1401) — the file already
|
|
1831
|
+
* carries that warning at `findMatchedRedirectEntry` in the consuming hook.
|
|
1832
|
+
*
|
|
1833
|
+
* An unexpandable `~` (no usable `home`) contributes NOTHING — fail-closed for
|
|
1834
|
+
* the home-anchored class, since an unexpanded `~` names no file.
|
|
1835
|
+
*
|
|
1836
|
+
* @param {string[]} denylist
|
|
1837
|
+
* @param {string|undefined} home
|
|
1838
|
+
* @returns {{ relRegexes: RegExp[], absRegexes: RegExp[] }}
|
|
1839
|
+
*/
|
|
1840
|
+
function compileTargetDenylist(denylist, home) {
|
|
1815
1841
|
const relRegexes = [];
|
|
1816
1842
|
const absRegexes = [];
|
|
1817
1843
|
for (const pattern of denylist) {
|
|
@@ -1824,38 +1850,48 @@ export function redirectRuleMatches(rule, command, opts = {}) {
|
|
|
1824
1850
|
relRegexes.push(redirectGlobToRegExp(text));
|
|
1825
1851
|
}
|
|
1826
1852
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
for (const entry of extractRedirectTargets(command)) {
|
|
1830
|
-
if (entry.unresolved) continue;
|
|
1831
|
-
if (!modes.has(entry.mode)) continue;
|
|
1853
|
+
return { relRegexes, absRegexes };
|
|
1854
|
+
}
|
|
1832
1855
|
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1856
|
+
/**
|
|
1857
|
+
* Test ONE resolved target text against a compiled denylist.
|
|
1858
|
+
*
|
|
1859
|
+
* A RELATIVE target is repo-root-relative in BOTH classes, exactly as the
|
|
1860
|
+
* repo-relative class already treats it (#994 R1) — otherwise
|
|
1861
|
+
* `../../.claude/projects/<enc>/memory/MEMORY.md` from the repo root names the
|
|
1862
|
+
* very file the `~` spelling blocks, and only the spelling would be denied
|
|
1863
|
+
* (measured 2026-09-13: relative `>`/`>>` ALLOW vs tilde/absolute BLOCK on one
|
|
1864
|
+
* and the same resolved path). This resolves TARGETS, never ENTRIES: a
|
|
1865
|
+
* repo-relative denylist entry still goes through `repoRelativeRedirectTarget`
|
|
1866
|
+
* and still cannot reach out of the root.
|
|
1867
|
+
*
|
|
1868
|
+
* @param {string} rawTarget — resolved target text (quotes already stripped)
|
|
1869
|
+
* @param {{ relRegexes: RegExp[], absRegexes: RegExp[] }} compiled
|
|
1870
|
+
* @param {string|null} repoRoot
|
|
1871
|
+
* @param {string|undefined} home
|
|
1872
|
+
* @returns {boolean}
|
|
1873
|
+
*/
|
|
1874
|
+
function targetMatchesDenylist(rawTarget, compiled, repoRoot, home) {
|
|
1875
|
+
const { relRegexes, absRegexes } = compiled;
|
|
1876
|
+
|
|
1877
|
+
if (absRegexes.length > 0) {
|
|
1878
|
+
const expandedTarget = expandLeadingHome(rawTarget, home);
|
|
1879
|
+
const absTarget = path.isAbsolute(expandedTarget)
|
|
1880
|
+
? expandedTarget
|
|
1881
|
+
: repoRoot && path.isAbsolute(repoRoot)
|
|
1882
|
+
? path.resolve(repoRoot, expandedTarget)
|
|
1883
|
+
: null;
|
|
1884
|
+
if (absTarget !== null) {
|
|
1885
|
+
const folded = foldAbsoluteTarget(absTarget);
|
|
1886
|
+
if (absRegexes.some((re) => re.test(folded))) return true;
|
|
1852
1887
|
}
|
|
1888
|
+
}
|
|
1853
1889
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
}
|
|
1890
|
+
if (relRegexes.length > 0) {
|
|
1891
|
+
const target = repoRelativeRedirectTarget(rawTarget, repoRoot, home);
|
|
1892
|
+
if (target !== null && relRegexes.some((re) => re.test(target))) return true;
|
|
1858
1893
|
}
|
|
1894
|
+
|
|
1859
1895
|
return false;
|
|
1860
1896
|
}
|
|
1861
1897
|
|
|
@@ -1875,6 +1911,217 @@ function foldAbsoluteTarget(abs) {
|
|
|
1875
1911
|
);
|
|
1876
1912
|
}
|
|
1877
1913
|
|
|
1914
|
+
/**
|
|
1915
|
+
* Verbs that REMOVE or RELOCATE a file named as a bare operand (#1401).
|
|
1916
|
+
*
|
|
1917
|
+
* `mv` is here for BOTH of its operand roles: the SOURCE disappears from its
|
|
1918
|
+
* path, and the DESTINATION is overwritten when it already exists — either one
|
|
1919
|
+
* loses the file that was there. So every operand of these verbs is a candidate,
|
|
1920
|
+
* with no source/destination distinction to get wrong.
|
|
1921
|
+
*
|
|
1922
|
+
* NAMED CEILING (BV-004) — measured 2026-09-19 against `splitChainSegments` +
|
|
1923
|
+
* `resolveSegmentVerb`. COVERED, because the verb resolves through the existing
|
|
1924
|
+
* machinery: bare `rm`/`mv`/`unlink`; `env X=1 rm …`, `sudo rm …`, `timeout 5 rm
|
|
1925
|
+
* …`, `nohup`/`nice`/`stdbuf`/`command`/`exec` (WRAPPER_UNWRAP); every chain
|
|
1926
|
+
* position (`;`, `&&`, `||`, `|`, `&`, newline) and compound-statement head
|
|
1927
|
+
* (`do rm …`, `{ rm …; }` — COMMAND_POSITION_KEYWORDS); and the literal payload
|
|
1928
|
+
* of `bash -c '…'` / `env -S '…'` / `eval '…'` via the shared payload recursion.
|
|
1929
|
+
*
|
|
1930
|
+
* NOT covered, deliberately, because the deleted path is not lexically an
|
|
1931
|
+
* operand of a resolvable verb:
|
|
1932
|
+
* - `find … -delete` / `find … -exec rm {} \;` — the verb is `find`; its path
|
|
1933
|
+
* operand is a SEARCH ROOT, and treating a search root as a delete target
|
|
1934
|
+
* would also flag a pure `find … -print` over the same directory.
|
|
1935
|
+
* - `… | xargs rm` — the targets arrive on stdin and do not exist as command
|
|
1936
|
+
* text at hook time. `xargs` is deliberately NOT in WRAPPER_UNWRAP (see
|
|
1937
|
+
* there: unwrapping it could only LOOSEN the guard).
|
|
1938
|
+
* - `git rm <path>` — the verb is `git`; a git-subverb grammar is a separate
|
|
1939
|
+
* table. PSA-007 already bans `git rm` for dispatched subagents.
|
|
1940
|
+
* - in-process deletion (`node -e 'fs.unlinkSync(…)'`) — not redirect- or
|
|
1941
|
+
* operand-shaped; same class boundary the `redirect-harness-memory`
|
|
1942
|
+
* rationale draws for `tee -a` and `fs.appendFileSync`.
|
|
1943
|
+
* REVISIT TRIGGER: any of those four shapes showing up in a real deletion
|
|
1944
|
+
* incident or a transcript census — extend this set or add the sibling grammar,
|
|
1945
|
+
* never special-case one command.
|
|
1946
|
+
*/
|
|
1947
|
+
const PATH_DESTRUCTIVE_VERBS = new Set(['rm', 'mv', 'unlink']);
|
|
1948
|
+
|
|
1949
|
+
/**
|
|
1950
|
+
* Recursive collector behind {@link extractDeleteTargets}. Mirrors
|
|
1951
|
+
* collectRedirectTargets' structure — same segment walk, same dual-reading
|
|
1952
|
+
* union, same depth/budget caps and the same fail-VISIBLE cut-off markers.
|
|
1953
|
+
*
|
|
1954
|
+
* @param {Array<Array<{ text: string, quoted: boolean, redirect?: object }>>} segments
|
|
1955
|
+
* @param {Array<object>} out — accumulator
|
|
1956
|
+
* @param {number} depth
|
|
1957
|
+
* @param {{ remaining: number }} budget
|
|
1958
|
+
*/
|
|
1959
|
+
function collectDeleteTargets(segments, out, depth, budget) {
|
|
1960
|
+
for (const segment of segments) {
|
|
1961
|
+
const resolved = resolveSegmentVerb(segment);
|
|
1962
|
+
|
|
1963
|
+
// BOTH readings of an ambiguous unknown WRAPPER flag contribute (#1000),
|
|
1964
|
+
// unioned and never replaced: `env -Q x rm .orchestrator/metrics/events.jsonl`
|
|
1965
|
+
// resolves to the non-destructive verb `x` in parse A and to `rm` in parse B,
|
|
1966
|
+
// while `sudo -n rm <file>` is the mirror case (parse B swallows `rm` as
|
|
1967
|
+
// `-n`'s operand). Picking either reading alone drops one of the two.
|
|
1968
|
+
const readings = resolved.alt ? [resolved, resolved.alt] : [resolved];
|
|
1969
|
+
const seen = new Set();
|
|
1970
|
+
|
|
1971
|
+
for (const reading of readings) {
|
|
1972
|
+
if (!reading.verb || reading.index < 0) continue;
|
|
1973
|
+
if (!PATH_DESTRUCTIVE_VERBS.has(reading.verb)) continue;
|
|
1974
|
+
// A verb that only exists inside a quoted token is inert literal text for
|
|
1975
|
+
// this segment — same rule commandHasRecursiveForceRm applies. An
|
|
1976
|
+
// interpreter's quoted payload is reached through the recursion below
|
|
1977
|
+
// instead, where it is re-tokenized as the command it really is.
|
|
1978
|
+
if (segment[reading.index].quoted) continue;
|
|
1979
|
+
|
|
1980
|
+
let seenDashDash = false;
|
|
1981
|
+
for (let i = reading.index + 1; i < segment.length; i++) {
|
|
1982
|
+
const tok = segment[i];
|
|
1983
|
+
// A redirect operand is a filename the SHELL consumes, not an operand of
|
|
1984
|
+
// the verb — `rm -f x > out.log` deletes `x`, it does not delete
|
|
1985
|
+
// `out.log` (the redirect surface judges that one).
|
|
1986
|
+
if (tok.redirect) { i = redirectSpanEnd(segment, i); continue; }
|
|
1987
|
+
if (!seenDashDash && tok.text === '--') { seenDashDash = true; continue; }
|
|
1988
|
+
// FLAG SKIPPING, and why this is the UNION of both flag readings rather
|
|
1989
|
+
// than a pick (#1000 applied to the VERB's own options): an option table
|
|
1990
|
+
// cannot enumerate every flag `rm`/`mv` may grow, and the two possible
|
|
1991
|
+
// readings of an unknown one are "boolean" (the next word is an operand)
|
|
1992
|
+
// and "value-taking" (the next word is the flag's value). The
|
|
1993
|
+
// value-taking reading is a strict SUBSET of the boolean one here — it
|
|
1994
|
+
// can only HIDE a word, never reveal one — so collecting under the
|
|
1995
|
+
// boolean reading yields exactly the union of both. That is also the
|
|
1996
|
+
// fail-closed direction: `mv -t .orchestrator/metrics/ x` reports the
|
|
1997
|
+
// `-t` operand as a candidate (it IS a destination that can overwrite),
|
|
1998
|
+
// where a value-taking reading would have swallowed it silently.
|
|
1999
|
+
if (!seenDashDash && !tok.quoted && tok.text.startsWith('-') && tok.text !== '-') continue;
|
|
2000
|
+
|
|
2001
|
+
const key = `${reading.verb}\u0000${tok.text}`;
|
|
2002
|
+
if (seen.has(key)) continue;
|
|
2003
|
+
seen.add(key);
|
|
2004
|
+
|
|
2005
|
+
if (/[$`]/.test(tok.text)) {
|
|
2006
|
+
// Variable indirection (`rm -f "$LEDGER"`) or command substitution:
|
|
2007
|
+
// fail-visible, never guess (#983 rule, same direction).
|
|
2008
|
+
out.push({
|
|
2009
|
+
target: null, verb: reading.verb, unresolved: true, reason: 'variable/substitution',
|
|
2010
|
+
});
|
|
2011
|
+
continue;
|
|
2012
|
+
}
|
|
2013
|
+
out.push({ target: tok.text, verb: reading.verb });
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// Literal `-c` / `env -S` / `eval` payload recursion — the same deduped
|
|
2018
|
+
// union helper and the same shared budget the match and redirect surfaces
|
|
2019
|
+
// use, so this traversal cannot be starved any differently than those.
|
|
2020
|
+
const payloads = dedupedSegmentPayloads(segment, resolved);
|
|
2021
|
+
if (payloads.length === 0) continue;
|
|
2022
|
+
|
|
2023
|
+
// A cap that drops payloads SILENTLY is a bypass, not a cap (#988 T2) —
|
|
2024
|
+
// emit a marker so the consuming hook can surface it.
|
|
2025
|
+
if (depth >= MAX_PAYLOAD_DEPTH) {
|
|
2026
|
+
out.push({ target: null, verb: null, unresolved: true, reason: 'depth-exceeded' });
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
for (const payload of payloads) {
|
|
2030
|
+
if (budget.remaining <= 0) {
|
|
2031
|
+
out.push({ target: null, verb: null, unresolved: true, reason: 'budget-exhausted' });
|
|
2032
|
+
break;
|
|
2033
|
+
}
|
|
2034
|
+
budget.remaining -= 1;
|
|
2035
|
+
const subTokens = tokenizeCommand(
|
|
2036
|
+
normalizeShellWhitespaceExpansions(payload, { expandSingleQuoted: true }),
|
|
2037
|
+
);
|
|
2038
|
+
collectDeleteTargets(splitSegments(subTokens), out, depth + 1, budget);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/**
|
|
2044
|
+
* Extract every path a command names as an operand of a file-destroying verb
|
|
2045
|
+
* (`rm`, `mv`, `unlink` — see {@link PATH_DESTRUCTIVE_VERBS} for the covered and
|
|
2046
|
+
* uncovered shapes), across all chain segments and literal shell payloads (#1401).
|
|
2047
|
+
*
|
|
2048
|
+
* This is the DELETE/RENAME sibling of `extractRedirectTargets`: that one answers
|
|
2049
|
+
* "which file does this command WRITE", this one "which file does it REMOVE or
|
|
2050
|
+
* MOVE". They are separate surfaces because a redirect target and a verb operand
|
|
2051
|
+
* are different grammar positions — `rm -f x > out.log` names one of each, and
|
|
2052
|
+
* the guard's 53,896-line telemetry loss (#1401) happened because only the write
|
|
2053
|
+
* surface was ever consulted.
|
|
2054
|
+
*
|
|
2055
|
+
* Entry shapes:
|
|
2056
|
+
* - `{ target: string, verb: 'rm'|'mv'|'unlink' }` — resolved operand, quotes
|
|
2057
|
+
* stripped by the tokenizer.
|
|
2058
|
+
* - `{ target: null, verb, unresolved: true, reason: 'variable/substitution' }`
|
|
2059
|
+
* — the operand carries a variable or a command substitution. Reported
|
|
2060
|
+
* fail-visible; never a match candidate (#641 FP class).
|
|
2061
|
+
* - `{ target: null, verb: null, unresolved: true,
|
|
2062
|
+
* reason: 'budget-exhausted'|'depth-exceeded' }` — a payload subtree was NOT
|
|
2063
|
+
* traversed because a recursion cap cut it off (#988 T2).
|
|
2064
|
+
*
|
|
2065
|
+
* @param {string} command
|
|
2066
|
+
* @returns {Array<{ target: string|null, verb: string|null, unresolved?: boolean, reason?: string }>}
|
|
2067
|
+
*/
|
|
2068
|
+
export function extractDeleteTargets(command) {
|
|
2069
|
+
if (typeof command !== 'string' || command.length === 0) return [];
|
|
2070
|
+
const out = [];
|
|
2071
|
+
const segments = splitSegments(tokenizeCommand(normalizeShellWhitespaceExpansions(command)));
|
|
2072
|
+
collectDeleteTargets(segments, out, 0, { remaining: MAX_PAYLOAD_EVALUATIONS });
|
|
2073
|
+
return out;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
/**
|
|
2077
|
+
* Return the FIRST collected delete/rename target a `path-delete` policy rule's
|
|
2078
|
+
* `target-denylist` matches, or `null` when none does (#1401).
|
|
2079
|
+
*
|
|
2080
|
+
* Returns the ENTRY, not a boolean, so the consuming hook can name the verb and
|
|
2081
|
+
* the target in its deny reason without re-probing through a boolean matcher —
|
|
2082
|
+
* the workaround `findMatchedRedirectEntry` needs, and which its own docblock
|
|
2083
|
+
* flags as one copy too many.
|
|
2084
|
+
*
|
|
2085
|
+
* Takes pre-collected `entries` rather than the raw command so the hook
|
|
2086
|
+
* tokenizes ONCE (it needs the same array for the unresolved-marker notices).
|
|
2087
|
+
*
|
|
2088
|
+
* PER-TARGET, never per-command: every entry is judged on its own, so a sibling
|
|
2089
|
+
* operand that is NOT on the denylist can never lift the verdict for one that
|
|
2090
|
+
* is. `rm -f .orchestrator/tmp/ok .orchestrator/metrics/events.jsonl` matches on
|
|
2091
|
+
* the second operand — the exemption is the ABSENCE of a denylist entry for
|
|
2092
|
+
* `.orchestrator/tmp/**`, never a whole-command escape clause. That shape is the
|
|
2093
|
+
* #1106 hole (`guard-design.md` § "Widening a matcher without narrowing its
|
|
2094
|
+
* bypass"), where an appended exempt statement lifted the verdict for the whole
|
|
2095
|
+
* command; it is structurally unreachable here.
|
|
2096
|
+
*
|
|
2097
|
+
* Denylist semantics (globs, the two classes, `~` handling, case-folding,
|
|
2098
|
+
* repo-root relativization) are IDENTICAL to `redirectRuleMatches` — both call
|
|
2099
|
+
* `compileTargetDenylist` / `targetMatchesDenylist`, so the two rule classes can
|
|
2100
|
+
* never drift apart on what a pattern means.
|
|
2101
|
+
*
|
|
2102
|
+
* @param {{ 'target-denylist'?: string[] }} rule
|
|
2103
|
+
* @param {Array<{ target: string|null, verb: string|null, unresolved?: boolean }>} entries
|
|
2104
|
+
* @param {{ repoRoot?: string|null, home?: string|undefined }} [opts]
|
|
2105
|
+
* @returns {{ target: string, verb: string }|null}
|
|
2106
|
+
*/
|
|
2107
|
+
export function firstDeniedDeleteTarget(rule, entries, opts = {}) {
|
|
2108
|
+
const { repoRoot = null, home = process.env.HOME } = opts;
|
|
2109
|
+
if (!rule || !Array.isArray(entries) || entries.length === 0) return null;
|
|
2110
|
+
const denylist = Array.isArray(rule['target-denylist']) ? rule['target-denylist'] : [];
|
|
2111
|
+
if (denylist.length === 0) return null;
|
|
2112
|
+
|
|
2113
|
+
const compiled = compileTargetDenylist(denylist, home);
|
|
2114
|
+
if (compiled.relRegexes.length === 0 && compiled.absRegexes.length === 0) return null;
|
|
2115
|
+
|
|
2116
|
+
for (const entry of entries) {
|
|
2117
|
+
if (entry.unresolved || typeof entry.target !== 'string') continue;
|
|
2118
|
+
if (targetMatchesDenylist(entry.target, compiled, repoRoot, home)) {
|
|
2119
|
+
return { target: entry.target, verb: entry.verb };
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
return null;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
1878
2125
|
/**
|
|
1879
2126
|
* Test whether a command string contains a blocked pattern with shell-aware
|
|
1880
2127
|
* boundaries AND a quoted-payload guard (#641).
|
|
@@ -14,6 +14,15 @@ import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
|
14
14
|
|
|
15
15
|
const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Default `dialectic.budget-tokens` — the INPUT ceiling of one derivation call
|
|
19
|
+
* (SSOT; `scripts/dialectic-deriver.mjs` `DEFAULT_BUDGET.input` imports it).
|
|
20
|
+
* It is an upper bound the pre-dispatch estimate aborts on, not a spend: card
|
|
21
|
+
* bodies + steering alone estimated ~12k tokens here and ~30k in a consumer
|
|
22
|
+
* repo, so the former default of 8000 aborted real runs (#1380).
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_BUDGET_TOKENS = 32000;
|
|
25
|
+
|
|
17
26
|
/**
|
|
18
27
|
* Parse the top-level `dialectic:` YAML block from markdown content.
|
|
19
28
|
* Independent of the `## Session Config` section boundary.
|
|
@@ -21,7 +30,7 @@ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
|
21
30
|
* Defaults:
|
|
22
31
|
* cadence: 5 (integer ≥ 0 — session count between auto-runs; 0 = disabled)
|
|
23
32
|
* model: 'haiku'
|
|
24
|
-
* budget-tokens:
|
|
33
|
+
* budget-tokens: DEFAULT_BUDGET_TOKENS (32000, integer ≥ 0)
|
|
25
34
|
*
|
|
26
35
|
* Throws if `model` is present but not one of haiku|sonnet|opus.
|
|
27
36
|
*
|
|
@@ -29,7 +38,7 @@ const ALLOWED_MODELS = new Set(['haiku', 'sonnet', 'opus']);
|
|
|
29
38
|
* @returns {{ cadence: number, model: string, "budget-tokens": number }}
|
|
30
39
|
*/
|
|
31
40
|
export function _parseDialectic(content) {
|
|
32
|
-
const defaults = { cadence: 5, model: 'haiku', 'budget-tokens':
|
|
41
|
+
const defaults = { cadence: 5, model: 'haiku', 'budget-tokens': DEFAULT_BUDGET_TOKENS };
|
|
33
42
|
|
|
34
43
|
const lines = preprocessBlockLines(content);
|
|
35
44
|
let inBlock = false;
|
|
@@ -49,7 +58,7 @@ export function _parseDialectic(content) {
|
|
|
49
58
|
|
|
50
59
|
let cadence = 5;
|
|
51
60
|
let model = 'haiku';
|
|
52
|
-
let budgetTokens =
|
|
61
|
+
let budgetTokens = DEFAULT_BUDGET_TOKENS;
|
|
53
62
|
|
|
54
63
|
for (const rawLine of blockLines) {
|
|
55
64
|
const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { matchBlockHeader } from './block-header.mjs';
|
|
2
|
+
import { preprocessBlockLines } from './block-preprocess.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gate.mjs — Parser for the top-level `gate:` YAML block
|
|
6
|
+
* (PRD `docs/prd/2026-09-20-prozessgruppen-kill-und-waisen-waechter.md` §4 A3,
|
|
7
|
+
* Epic #1425 / issue #1432).
|
|
8
|
+
*
|
|
9
|
+
* Holds the wall-clock ceiling for GATE PATH B — the
|
|
10
|
+
* `scripts/run-quality-gate.mjs` → `scripts/lib/gates/gate-*.mjs` route, which
|
|
11
|
+
* had NO timeout at all before #1425 A3. Path A (`scripts/lib/quality-gate.mjs`)
|
|
12
|
+
* keeps its own `SO_GATE_TIMEOUT_MS` operator override; this block sets the
|
|
13
|
+
* COMMITTED default for path B so both paths may run equally long.
|
|
14
|
+
*
|
|
15
|
+
* Precedence in `run-quality-gate.mjs`: `SO_GATE_TIMEOUT_MS` (operator, wins) >
|
|
16
|
+
* this committed value > `DEFAULT_GATE_TIMEOUT_MS` (900 000).
|
|
17
|
+
*
|
|
18
|
+
* Tolerant parser: malformed values silently fall back to the default.
|
|
19
|
+
*
|
|
20
|
+
* Returns `{ 'timeout-path-b-ms': number }`.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** 900 000 ms (15 min) — same ceiling as `GATE_TIMEOUT_MS` on path A (PRD §4). */
|
|
24
|
+
const DEFAULT_TIMEOUT_PATH_B_MS = 900_000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse the top-level `gate:` YAML block from markdown content.
|
|
28
|
+
* Independent of the `## Session Config` section boundary.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} content — full file contents
|
|
31
|
+
* @returns {{'timeout-path-b-ms': number}}
|
|
32
|
+
*/
|
|
33
|
+
export function _parseGate(content) {
|
|
34
|
+
let timeoutPathBMs = DEFAULT_TIMEOUT_PATH_B_MS;
|
|
35
|
+
|
|
36
|
+
const lines = preprocessBlockLines(content);
|
|
37
|
+
let inBlock = false;
|
|
38
|
+
const blockLines = [];
|
|
39
|
+
|
|
40
|
+
for (const rawLine of lines) {
|
|
41
|
+
const line = rawLine.replace(/\r$/, '');
|
|
42
|
+
if (!inBlock) {
|
|
43
|
+
if (matchBlockHeader(line, 'gate')) inBlock = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (line.length > 0 && !/^\s/.test(line)) break;
|
|
47
|
+
blockLines.push(line);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const rawLine of blockLines) {
|
|
51
|
+
const clean = rawLine.replace(/\s*#.*$/, '').replace(/\s+$/, '');
|
|
52
|
+
if (!clean.trim()) continue;
|
|
53
|
+
|
|
54
|
+
const kvMatch = clean.match(/^\s+([a-zA-Z_-]+):\s*(.*)/);
|
|
55
|
+
if (!kvMatch) continue;
|
|
56
|
+
|
|
57
|
+
const k = kvMatch[1];
|
|
58
|
+
let v = kvMatch[2].trim();
|
|
59
|
+
if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) v = v.slice(1, -1);
|
|
60
|
+
else if (v.startsWith("'") && v.endsWith("'") && v.length >= 2) v = v.slice(1, -1);
|
|
61
|
+
|
|
62
|
+
if (k === 'timeout-path-b-ms') {
|
|
63
|
+
// A zero or negative ceiling would kill every command instantly — the
|
|
64
|
+
// failure mode is worse than the uncapped state this key exists to fix,
|
|
65
|
+
// so it falls back to the default rather than being honoured.
|
|
66
|
+
if (/^-?\d+$/.test(v)) {
|
|
67
|
+
const n = Number.parseInt(v, 10);
|
|
68
|
+
if (Number.isFinite(n) && n > 0) timeoutPathBMs = n;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return { 'timeout-path-b-ms': timeoutPathBMs };
|
|
74
|
+
}
|
|
@@ -13,6 +13,17 @@
|
|
|
13
13
|
* 3. committed Session Config default (the value the committed CLAUDE.md —
|
|
14
14
|
* AGENTS.md on Codex CLI — produced)
|
|
15
15
|
*
|
|
16
|
+
* `vault-dir` has one more tier (agents/vault#319): an owner.yaml `vault-dirs:`
|
|
17
|
+
* cwd match between env and paths.vault-dir. That tier lives in
|
|
18
|
+
* `resolveVaultDir` in scripts/lib/config.mjs, NOT here: this module is one of
|
|
19
|
+
* the three direct CP11 helpers check-owner-leakage.mjs imports, and a standalone
|
|
20
|
+
* vendored copy carries only those three files. A new import edge from here
|
|
21
|
+
* (e.g. to named-baseline-resolver.mjs) makes CP11 fail closed in that copy.
|
|
22
|
+
*
|
|
23
|
+
* `resolveVaultIntegrationHost` is the host-local switch for the
|
|
24
|
+
* `vault-integration` gate itself (SO#1448). It may only LOWER the committed
|
|
25
|
+
* level (strict → warn → off), never raise it.
|
|
26
|
+
*
|
|
16
27
|
* SYNCHRONOUS by design: `parseSessionConfig` in scripts/lib/config.mjs is sync,
|
|
17
28
|
* so this layer reuses the SYNC owner loader (`loadOwnerConfig`) and exposes only
|
|
18
29
|
* sync functions. An empty/whitespace value at any tier is treated as "unset" and
|
|
@@ -90,3 +101,71 @@ export function resolveHostPath(key, committedDefault, { env = process.env, owne
|
|
|
90
101
|
|
|
91
102
|
return committedDefault;
|
|
92
103
|
}
|
|
104
|
+
|
|
105
|
+
/** vault-integration levels, weakest first. The index order IS the ordering. */
|
|
106
|
+
const VAULT_INTEGRATION_LEVELS = /** @type {readonly string[]} */ (['off', 'warn', 'strict']);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {unknown} v
|
|
110
|
+
* @returns {'off'|'warn'|'strict'|undefined} a valid level, or undefined for unset/invalid
|
|
111
|
+
*/
|
|
112
|
+
function coerceVaultIntegrationLevel(v) {
|
|
113
|
+
if (typeof v !== 'string') return undefined;
|
|
114
|
+
const lower = v.trim().toLowerCase();
|
|
115
|
+
return VAULT_INTEGRATION_LEVELS.includes(lower)
|
|
116
|
+
? /** @type {'off'|'warn'|'strict'} */ (lower)
|
|
117
|
+
: undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Overlay the host-local `vault-integration` switch onto the committed block
|
|
122
|
+
* (SO#1448). Precedence: env `SO_VAULT_INTEGRATION=off|warn|strict` > owner.yaml
|
|
123
|
+
* `vault-integration: { enabled: false | mode: off|warn|strict }` > committed.
|
|
124
|
+
* An invalid value at a tier counts as unset and falls through, mirroring
|
|
125
|
+
* resolveDispatcherAutonomy in dispatcher-autonomy.mjs.
|
|
126
|
+
*
|
|
127
|
+
* LOWER-ONLY: the host's level is applied only when it is weaker than the
|
|
128
|
+
* committed one. A host that could raise it would mirror repos into the vault
|
|
129
|
+
* that never opted in, so `enabled: true` or a stronger mode is ignored.
|
|
130
|
+
* Applying `off` sets both `enabled: false` and `mode: 'off'`, so either
|
|
131
|
+
* consumer check (`enabled`, `mode != off`) sees the integration as off.
|
|
132
|
+
*
|
|
133
|
+
* @param {{ enabled?: boolean, mode?: string } & Record<string, unknown>} vi — parsed committed block
|
|
134
|
+
* @param {{ env?: Record<string, string|undefined>, ownerConfig?: object }} [ctx] — from loadHostPaths()
|
|
135
|
+
* @returns {Record<string, unknown> & { enabled?: boolean, mode?: string, 'host-override': 'env:SO_VAULT_INTEGRATION'|'owner.yaml'|null }}
|
|
136
|
+
* a copy of `vi`; `host-override` names the tier that lowered it, `null` when none did
|
|
137
|
+
*/
|
|
138
|
+
export function resolveVaultIntegrationHost(vi, { env = process.env, ownerConfig } = {}) {
|
|
139
|
+
/** @type {Record<string, unknown> & { enabled?: boolean, mode?: string, 'host-override': 'env:SO_VAULT_INTEGRATION'|'owner.yaml'|null }} */
|
|
140
|
+
const result = { ...vi, 'host-override': null };
|
|
141
|
+
|
|
142
|
+
/** @type {'off'|'warn'|'strict'|undefined} */
|
|
143
|
+
let requested = coerceVaultIntegrationLevel(env?.SO_VAULT_INTEGRATION);
|
|
144
|
+
/** @type {'env:SO_VAULT_INTEGRATION'|'owner.yaml'} */
|
|
145
|
+
let source = 'env:SO_VAULT_INTEGRATION';
|
|
146
|
+
if (requested === undefined) {
|
|
147
|
+
const owner = ownerConfig?.['vault-integration'];
|
|
148
|
+
if (owner !== null && typeof owner === 'object' && !Array.isArray(owner)) {
|
|
149
|
+
requested = owner.enabled === false ? 'off' : coerceVaultIntegrationLevel(owner.mode);
|
|
150
|
+
source = 'owner.yaml';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (requested === undefined) return result;
|
|
154
|
+
|
|
155
|
+
const committedLevel =
|
|
156
|
+
vi?.enabled === true ? (coerceVaultIntegrationLevel(vi.mode) ?? 'warn') : 'off';
|
|
157
|
+
if (
|
|
158
|
+
VAULT_INTEGRATION_LEVELS.indexOf(requested) >= VAULT_INTEGRATION_LEVELS.indexOf(committedLevel)
|
|
159
|
+
) {
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (requested === 'off') {
|
|
164
|
+
result.enabled = false;
|
|
165
|
+
result.mode = 'off';
|
|
166
|
+
} else {
|
|
167
|
+
result.mode = requested;
|
|
168
|
+
}
|
|
169
|
+
result['host-override'] = source;
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* after — 'quality' | 'impl-polish' (default: 'quality')
|
|
11
11
|
* threshold — string spec parsed via parseThreshold (default: 'all')
|
|
12
12
|
* personas — string[] (each matches SAFE_PERSONA_NAME_RE) (default: [])
|
|
13
|
-
* dispatch-model — model alias or full ID (default: '
|
|
13
|
+
* dispatch-model — model alias or full ID (default: 'opus' — tracks the current Opus)
|
|
14
14
|
* mode — 'off' | 'warn' | 'strict' (default: 'off')
|
|
15
15
|
*
|
|
16
16
|
* Used by skills/wave-executor/wave-loop.md § 3b: Persona-Gate Hook to decide
|
|
@@ -32,7 +32,7 @@ const DEFAULTS = Object.freeze({
|
|
|
32
32
|
threshold: 'all',
|
|
33
33
|
threshold_parsed: null, // cached ParsedThreshold result; populated by _normalizePersonaGateWave
|
|
34
34
|
personas: [],
|
|
35
|
-
'dispatch-model': '
|
|
35
|
+
'dispatch-model': 'opus',
|
|
36
36
|
mode: 'off',
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -138,7 +138,7 @@ export function _normalizePersonaGateWave(parsed) {
|
|
|
138
138
|
}
|
|
139
139
|
if (!ALLOWED_MODEL_ALIASES.has(m) && !MODEL_ID_RE.test(m)) {
|
|
140
140
|
throw new Error(
|
|
141
|
-
`persona-gate-wave.dispatch-model must be one of ${[...ALLOWED_MODEL_ALIASES].map((a) => JSON.stringify(a)).join('|')} or a full model ID like "claude-opus-
|
|
141
|
+
`persona-gate-wave.dispatch-model must be one of ${[...ALLOWED_MODEL_ALIASES].map((a) => JSON.stringify(a)).join('|')} or a full model ID like "claude-opus-5-5" (got ${JSON.stringify(m)})`,
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
out['dispatch-model'] = m;
|