session-orchestrator 5.1.0 → 5.3.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 +6 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +7 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +8 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +21 -0
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +7 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +7 -2
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +7 -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 +7 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +7 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +8 -3
- 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 +22 -0
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +7 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +20 -0
- 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 +6 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- 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 +6 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +8 -2
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +21 -0
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +6 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +22 -0
- 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 +6 -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 +6 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +7 -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 +21 -0
- package/.agents/skills/test/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 +7 -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/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +13 -4
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +197 -0
- package/README.md +11 -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 +17 -3
- package/docs/baseline.md +12 -6
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +15 -3
- package/docs/components.md +13 -6
- package/docs/events-schema.md +59 -9
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +135 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +106 -11
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +125 -8
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- 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.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +72 -25
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +211 -23
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +6 -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 +200 -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 +18 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +119 -28
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +385 -5
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/hooks/subagent-telemetry.mjs +15 -19
- 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 +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +2 -2
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +171 -15
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +77 -32
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +220 -11
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- 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/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +408 -33
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +76 -13
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- 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 +524 -5
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +332 -50
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/is-main-module.mjs +82 -0
- 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/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +122 -91
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
- 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/rule-loader.mjs +41 -12
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +429 -7
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +91 -12
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- 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/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tail-window.mjs +56 -0
- 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 +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
- package/scripts/lib/user-invocable-skills.mjs +205 -0
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +329 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- 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 +35 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
- package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
- package/scripts/lib/validate/check-unwired-features.mjs +220 -33
- package/scripts/lib/validate/check-validator-registration.mjs +36 -12
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +39 -12
- 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-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +109 -39
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +227 -0
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +11 -4
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +80 -11
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- 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/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +52 -23
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +9 -2
- package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +2 -2
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +21 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +18 -6
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +20 -2
- package/skills/wave-executor/SKILL.md +14 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
package/hooks/enforce-scope.mjs
CHANGED
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
* matches the fully realpath-resolved candidate → allow, BEFORE G6.
|
|
22
22
|
* Runs before G6 so a deliberate out-of-repo grant (e.g. a vault path)
|
|
23
23
|
* is reachable at all — G6 would otherwise deny every out-of-repo path
|
|
24
|
-
* without ever consulting allowedPaths. See
|
|
24
|
+
* without ever consulting allowedPaths. See matchedAbsoluteGrant.
|
|
25
|
+
* (#1398 cond. 4) The matched grant is GRADED through the shared
|
|
26
|
+
* `gradeScopeEntry` predicate; an `error` verdict emits ONE WARN and the
|
|
27
|
+
* write is still ALLOWED — this gate never denies on a grading verdict.
|
|
25
28
|
* G5c (#1295) out-of-root carveout for THIS repo's Claude Code auto-memory
|
|
26
29
|
* directory (`~/.claude/projects/<encoded-repo-path>/memory/`). Harness-
|
|
27
30
|
* owned, lives outside the working copy, cannot collide with any wave
|
|
@@ -68,8 +71,11 @@ import { promises as fs } from 'node:fs';
|
|
|
68
71
|
import { pathToFileURL } from 'node:url';
|
|
69
72
|
|
|
70
73
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
71
|
-
//
|
|
72
|
-
|
|
74
|
+
// Static for the SAME reason profile-gate.mjs is (#993, see the late-binding
|
|
75
|
+
// block below): a leaf predicate with ZERO repo imports (node:fs + node:url
|
|
76
|
+
// only) that decides whether this hook runs at all. Everything carrying a
|
|
77
|
+
// transitive repo graph stays late-bound inside bootstrap().
|
|
78
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
73
79
|
|
|
74
80
|
// ---------------------------------------------------------------------------
|
|
75
81
|
// #993 — late-bound repo dependencies
|
|
@@ -109,6 +115,12 @@ let readJson;
|
|
|
109
115
|
let classifyEmptyScope;
|
|
110
116
|
let suggestForEmptyScope;
|
|
111
117
|
let sessionStartedAtMs;
|
|
118
|
+
// #1398 cond. 4 — THE shared grading predicate for an absolute Gate 5b grant,
|
|
119
|
+
// plus the resolver it takes. The hook WARNS on an `error` verdict and never
|
|
120
|
+
// denies on it; see Gate 5b. Both come from the module already bound below —
|
|
121
|
+
// no new module edge, so `hooks/_lib/hook-import-set.json` is unchanged.
|
|
122
|
+
let gradeScopeEntry;
|
|
123
|
+
let canonicalizeGrantPrefix;
|
|
112
124
|
// #1123 — "is this manifest even mine?" (G3b). Process-local identity only
|
|
113
125
|
// (#1194): the repo-global `session.lock` tier is shared by every session in the
|
|
114
126
|
// checkout and would classify a peer's manifest as ours.
|
|
@@ -188,7 +200,13 @@ async function bootstrap() {
|
|
|
188
200
|
({ resolveProjectDir } = modules.platform);
|
|
189
201
|
({ findScopeFile, pathMatchesPattern, suggestForScopeViolation } = modules.hardening);
|
|
190
202
|
({ readJson } = modules.common);
|
|
191
|
-
({
|
|
203
|
+
({
|
|
204
|
+
classifyEmptyScope,
|
|
205
|
+
suggestForEmptyScope,
|
|
206
|
+
sessionStartedAtMs,
|
|
207
|
+
gradeScopeEntry,
|
|
208
|
+
canonicalizeGrantPrefix,
|
|
209
|
+
} = modules.scopeGate);
|
|
192
210
|
({ readProcessLocalSessionIds, classifyManifestSession } = modules.sessionIdentity);
|
|
193
211
|
}
|
|
194
212
|
|
|
@@ -433,7 +451,52 @@ async function main() {
|
|
|
433
451
|
// out-of-repo path before allowedPaths is ever consulted. Honour such grants
|
|
434
452
|
// here — matching ONLY absolute entries against the fully realpath-resolved
|
|
435
453
|
// candidate, so relative entries can never be used to escape the repo (REQ-09).
|
|
436
|
-
|
|
454
|
+
//
|
|
455
|
+
// GRADING (#1398 acceptance condition 4): this gate ALLOWS exactly what it
|
|
456
|
+
// allowed before — the verdict below is a WARN, never a deny. Until now the
|
|
457
|
+
// asymmetry was silent: `scripts/validate-wave-scope.mjs` graded these grants
|
|
458
|
+
// (system-root denylist, home depth + sensitive-subdirectory rule) and NOTHING
|
|
459
|
+
// called it from code — four prose steps in `skills/wave-executor/` were the
|
|
460
|
+
// only thing between a manifest and this gate, so a manifest that skipped them
|
|
461
|
+
// reached here ungraded and the operator never learned that it had.
|
|
462
|
+
//
|
|
463
|
+
// Now the same predicate runs HERE, on the grant that actually matched, and an
|
|
464
|
+
// `error` verdict surfaces as one operator-visible notice. Deliberately not a
|
|
465
|
+
// deny: this hook runs in every repo on the host, the grant is the
|
|
466
|
+
// coordinator's own artefact, and turning a live allow into a block on a
|
|
467
|
+
// pre-flight rule would break working sessions to enforce a policy whose place
|
|
468
|
+
// is before dispatch. `emitWarn` is an ALLOW that carries a notice (exit 0,
|
|
469
|
+
// `systemMessage` only) — see `scripts/lib/io.mjs`.
|
|
470
|
+
//
|
|
471
|
+
// THE SAME `resolve` THE VALIDATOR PASSES (#1398 cond. 4, closed 2026-09-21).
|
|
472
|
+
// It was omitted here on the assumption that canonicalisation is too expensive
|
|
473
|
+
// for a PreToolUse hot path; measuring it refuted that, and the omission was
|
|
474
|
+
// the last source of a verdict divergence between the two callers of one
|
|
475
|
+
// predicate (`/tmp/x/**`: `warn` here, `error/non-canonical` there — silent on
|
|
476
|
+
// exactly the grant that matches nothing at this gate).
|
|
477
|
+
//
|
|
478
|
+
// MEASURED 2026-09-21, A/B in one process, 200 repetitions, median:
|
|
479
|
+
// - this call site grades ONE grant per Gate 5b hit, never the manifest:
|
|
480
|
+
// +0.067 ms, 15 `realpathSync` — 75× under the 5 ms decision threshold;
|
|
481
|
+
// - a Gate 5b hit requires an out-of-repo write AND an absolute grant that
|
|
482
|
+
// matches it. The live 64-entry manifest of that session carried 0
|
|
483
|
+
// absolute entries and therefore cost 0 syscalls: an ordinary in-repo
|
|
484
|
+
// write never reaches this line at all.
|
|
485
|
+
// `canonicalizeGrantPrefix` memoizes per process and never throws (see its
|
|
486
|
+
// docblock); `gradeScopeEntry` additionally degrades a throwing resolver to
|
|
487
|
+
// the literal spelling, so the worst case is today's verdict.
|
|
488
|
+
const matchedGrant = matchedAbsoluteGrant(resolvedPath, allowedPaths);
|
|
489
|
+
if (matchedGrant !== null) {
|
|
490
|
+
const grade = gradeScopeEntry(matchedGrant, { resolve: canonicalizeGrantPrefix });
|
|
491
|
+
if (grade?.verdict === 'error') {
|
|
492
|
+
return emitWarn(
|
|
493
|
+
`Gate 5b honoured an out-of-repo grant that scripts/validate-wave-scope.mjs would REFUSE ` +
|
|
494
|
+
`before dispatch — allowedPaths ${grade.message}. The write to '${resolvedPath}' is ` +
|
|
495
|
+
`ALLOWED (this gate never denies on a grading verdict); re-validate the manifest.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
return emitAllow();
|
|
499
|
+
}
|
|
437
500
|
|
|
438
501
|
// Gate 6: path must be inside the project root
|
|
439
502
|
if (!isPathInside(resolvedPath, projectRoot)) {
|
|
@@ -774,19 +837,54 @@ function isCoordinatorCarveout(normalizedRel, projectRoot, scopePath) {
|
|
|
774
837
|
* SECURITY (REQ-09): matches ONLY entries that are themselves absolute, against
|
|
775
838
|
* the fully realpath-resolved candidate — so RELATIVE entries (`**`, `../**`)
|
|
776
839
|
* can NEVER be used to escape the repo. When no absolute entry exists the helper
|
|
777
|
-
* returns
|
|
840
|
+
* returns null and the caller falls through to Gate 6 unchanged (inert pre-gate).
|
|
778
841
|
* An absolute entry matches only its own literal (canonical/realpath) subtree;
|
|
779
842
|
* the operator is responsible for supplying a canonical absolute path.
|
|
780
843
|
*
|
|
844
|
+
* MATCHING IS SHAPE-BLIND HERE; GRADING IS SHARED (#1398 cond. 4). This helper
|
|
845
|
+
* still decides nothing about whether a grant SHOULD exist: `/Users/<u>/**` and
|
|
846
|
+
* `/Users/<u>/.ssh/**` match exactly like `/Users/<u>/Projects/vault/**`. What
|
|
847
|
+
* changed is that the CALLER now runs the ONE grading predicate —
|
|
848
|
+
* `gradeScopeEntry` in `scripts/lib/scope-gate.mjs`, the same function
|
|
849
|
+
* `scripts/validate-wave-scope.mjs` turns into an exit-1 refusal — over the grant
|
|
850
|
+
* this helper returns, and WARNS when it grades `error`.
|
|
851
|
+
*
|
|
852
|
+
* ONE PREDICATE, ONE ARGUMENT SHAPE — THE VERDICTS NO LONGER DIFFER (#1398
|
|
853
|
+
* cond. 4, closed 2026-09-21). Both callers now pass the SAME fs-backed
|
|
854
|
+
* `resolve` (`canonicalizeGrantPrefix`, exported from
|
|
855
|
+
* `scripts/lib/scope-gate.mjs`), so only the CONSEQUENCE is asymmetric: the
|
|
856
|
+
* validator refuses an `error` before dispatch, this gate allows it with a
|
|
857
|
+
* notice at write time. The history, because the asymmetry was load-bearing for
|
|
858
|
+
* two fixes, measured 2026-09-20 @ 7e110a2a with the resolver-free call:
|
|
859
|
+
* - `/private/etc/**`, `/private/var/**` — the macOS realpaths of the
|
|
860
|
+
* denylisted `/etc` and `/var`, i.e. the spellings Gate 5b ACTUALLY matches
|
|
861
|
+
* (it matches the realpath-resolved candidate): hook `warn`, validator
|
|
862
|
+
* `error`. The notice fired on `/etc/**`, which reaches nothing here, and
|
|
863
|
+
* stayed silent on the spelling that reaches everything. CLOSED by listing
|
|
864
|
+
* the two aliases literally (`DENIED_ABSOLUTE_ALIAS_ROOTS`), at zero
|
|
865
|
+
* syscalls — they still cost nothing and stay.
|
|
866
|
+
* - `/tmp/x/**` — hook `warn`, validator `error/non-canonical`: the last
|
|
867
|
+
* divergence, 1 of 9 probes, and not closable by any literal list, because
|
|
868
|
+
* proving a literal prefix resolves elsewhere IS the realpath call. CLOSED
|
|
869
|
+
* by passing the resolver here too, once its cost was measured instead of
|
|
870
|
+
* assumed (+0.067 ms per Gate 5b hit; see the call site).
|
|
871
|
+
* The parity of the two verdict tables is pinned by
|
|
872
|
+
* `tests/lib/scope-gate.test.mjs` § "hook / validator grading parity", so a NEW
|
|
873
|
+
* divergence is a red test rather than a comment someone has to notice.
|
|
874
|
+
*
|
|
875
|
+
* Returns the MATCHED PATTERN rather than a boolean precisely so the caller has
|
|
876
|
+
* something to grade: with a bare `true` the grant that opened the gate is
|
|
877
|
+
* unknowable at the call site, and any notice would have to re-derive it.
|
|
878
|
+
*
|
|
781
879
|
* @param {string} resolvedPath — fully realpath-resolved candidate (absolute)
|
|
782
880
|
* @param {string[]} allowedPaths — raw allowedPaths array from wave-scope.json
|
|
783
|
-
* @returns {
|
|
881
|
+
* @returns {string|null} the first matching absolute entry, or null
|
|
784
882
|
*/
|
|
785
|
-
function
|
|
883
|
+
function matchedAbsoluteGrant(resolvedPath, allowedPaths) {
|
|
786
884
|
const abs = allowedPaths.filter((p) => typeof p === 'string' && path.isAbsolute(p));
|
|
787
|
-
if (abs.length === 0) return
|
|
885
|
+
if (abs.length === 0) return null;
|
|
788
886
|
const normalizedAbs = resolvedPath.split(path.sep).join('/');
|
|
789
|
-
return abs.
|
|
887
|
+
return abs.find((pat) => pathMatchesPattern(normalizedAbs, pat.split(path.sep).join('/'))) ?? null;
|
|
790
888
|
}
|
|
791
889
|
|
|
792
890
|
// ---------------------------------------------------------------------------
|
|
@@ -804,29 +902,41 @@ function matchesAbsoluteAllowlist(resolvedPath, allowedPaths) {
|
|
|
804
902
|
// guard armed and then tripped over a specific path; that fails CLOSED via
|
|
805
903
|
// emitDeny (SECURITY-REQ-01). The two paths MUST stay separate.
|
|
806
904
|
// ---------------------------------------------------------------------------
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
905
|
+
// Entry guard (#1393): run only when this file IS the script node was invoked
|
|
906
|
+
// with — every harness path execs it (`sh run-node.sh <this file>`). A bare
|
|
907
|
+
// `import()` (a probe, a test, a curious agent) must neither run main() nor
|
|
908
|
+
// tear the importing process down. The profile gate sits INSIDE the guard for
|
|
909
|
+
// that second reason: at module top level its `process.exit(0)` exited every
|
|
910
|
+
// process that merely imported this hook. bootstrap() is inside too — loading
|
|
911
|
+
// the guard sources is work a disabled hook and a bare importer must not do.
|
|
912
|
+
if (isMainModule(import.meta.url)) {
|
|
913
|
+
// #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
|
|
914
|
+
if (!shouldRunHook('enforce-scope')) process.exit(0);
|
|
915
|
+
|
|
810
916
|
try {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
917
|
+
await bootstrap();
|
|
918
|
+
} catch (loadError) {
|
|
919
|
+
try {
|
|
920
|
+
const { emitGuardInactiveBanner } = await import('./_lib/guard-source-loader.mjs');
|
|
921
|
+
// hookName is threaded explicitly (#993 — no hard-wired literal in the loader).
|
|
922
|
+
emitGuardInactiveBanner({ hookName: HOOK_NAME, error: loadError, consequence: GUARD_CONSEQUENCE });
|
|
923
|
+
} catch {
|
|
924
|
+
// Last resort: even the banner helper failed to load. Emit unconditionally —
|
|
925
|
+
// repeated noise beats a silent disarm.
|
|
926
|
+
process.stderr.write(
|
|
927
|
+
'🚨 enforce-scope: GUARD INACTIVE — module load failed ' +
|
|
928
|
+
`(${String(loadError?.message || loadError).split('\n')[0]}). ` +
|
|
929
|
+
'Edit/Write/MultiEdit scope enforcement is OFF. See issue #993.\n'
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
process.exit(0); // fail-open, but no longer fail-silent
|
|
822
933
|
}
|
|
823
|
-
process.exit(0); // fail-open, but no longer fail-silent
|
|
824
|
-
}
|
|
825
934
|
|
|
826
|
-
// SECURITY-REQ-01 (fail-closed): any unhandled rejection → structured deny, never bare exit 1
|
|
827
|
-
main().catch((e) => {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
});
|
|
935
|
+
// SECURITY-REQ-01 (fail-closed): any unhandled rejection → structured deny, never bare exit 1
|
|
936
|
+
main().catch((e) => {
|
|
937
|
+
emitDeny(
|
|
938
|
+
'Internal hook error — request blocked for safety',
|
|
939
|
+
`${e?.message ?? String(e)}`,
|
|
940
|
+
);
|
|
941
|
+
});
|
|
942
|
+
}
|
package/hooks/hooks-codex.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
10
|
-
"command": "echo '🎯 Session Orchestrator v5.
|
|
10
|
+
"command": "echo '🎯 Session Orchestrator v5.3.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
|
|
11
11
|
"async": false
|
|
12
12
|
},
|
|
13
13
|
{
|
package/hooks/hooks.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "echo '🎯 Session Orchestrator v5.
|
|
9
|
+
"command": "echo '🎯 Session Orchestrator v5.3.0 — /session [housekeeping|feature|deep] | /plan [new|feature|retro] | /discovery [scope] | /evolve [analyze|review|list]'",
|
|
10
10
|
"async": false
|
|
11
11
|
},
|
|
12
12
|
{
|
package/hooks/loop-guard.mjs
CHANGED
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
28
|
-
|
|
29
|
-
if (!shouldRunHook('loop-guard')) process.exit(0);
|
|
28
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
30
29
|
|
|
31
30
|
import { promises as fs } from 'node:fs';
|
|
32
31
|
import os from 'node:os';
|
|
@@ -257,4 +256,13 @@ async function main() {
|
|
|
257
256
|
}
|
|
258
257
|
|
|
259
258
|
// Exit 0 always — informational hook must never block Claude (#619).
|
|
260
|
-
|
|
259
|
+
// Entry guard (#1393): run only when this file IS the script node was invoked
|
|
260
|
+
// with — every harness path execs it (`sh run-node.sh <this file>`). A bare
|
|
261
|
+
// `import()` (a probe, a test, a curious agent) must neither run main() nor
|
|
262
|
+
// tear the importing process down. The profile gate sits INSIDE the guard for
|
|
263
|
+
// that second reason: at module top level its `process.exit(0)` exited every
|
|
264
|
+
// process that merely imported this hook.
|
|
265
|
+
if (isMainModule(import.meta.url)) {
|
|
266
|
+
if (!shouldRunHook('loop-guard')) process.exit(0);
|
|
267
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
|
268
|
+
}
|
package/hooks/on-session-end.mjs
CHANGED
|
@@ -31,8 +31,6 @@ import { promises as fs } from 'node:fs';
|
|
|
31
31
|
import path from 'node:path';
|
|
32
32
|
|
|
33
33
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
34
|
-
// #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
|
|
35
|
-
if (!shouldRunHook('on-session-end')) process.exit(0);
|
|
36
34
|
|
|
37
35
|
import { emitEvent } from '../scripts/lib/events.mjs';
|
|
38
36
|
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
@@ -47,6 +45,7 @@ import {
|
|
|
47
45
|
OWNER_PROOF_RELPATH,
|
|
48
46
|
} from '../scripts/lib/session-lock.mjs';
|
|
49
47
|
import { parseSessionId } from '../scripts/lib/session-id.mjs';
|
|
48
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
50
49
|
import { deregisterSelf, logSweepEvent } from '../scripts/lib/session-registry.mjs';
|
|
51
50
|
import { readConfigFile, parseSessionConfig } from '../scripts/lib/config.mjs';
|
|
52
51
|
import { flush } from '../scripts/lib/telemetry/sync.mjs';
|
|
@@ -255,21 +254,44 @@ async function readPersistence(projectRoot) {
|
|
|
255
254
|
}
|
|
256
255
|
|
|
257
256
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
257
|
+
* Characters kept from `flush()`'s raw `reason` in the event record.
|
|
258
|
+
*
|
|
259
|
+
* NAMED CEILING (BV-004): `flush()` may return `build-error: <message>`, i.e.
|
|
260
|
+
* unbounded free text, into a stream whose purpose is aggregation. 200 mirrors
|
|
261
|
+
* the sibling bound in `scripts/lib/session-start-probes.mjs`; the banner
|
|
262
|
+
* re-bounds to 120 and strips control bytes on its own side, because the ledger
|
|
263
|
+
* is an untrusted string source for anything that renders it.
|
|
264
|
+
* REVISIT TRIGGER: a legitimate `flush()` reason longer than 200 characters.
|
|
265
|
+
*/
|
|
266
|
+
const FLUSH_REASON_MAX_CHARS = 200;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Reduce a `flush()` result to the three-field breadcrumb the event carries.
|
|
270
|
+
*
|
|
271
|
+
* `reason` is the FULL reason (length-bounded, see FLUSH_REASON_MAX_CHARS) and
|
|
272
|
+
* `reason_class` is its head token — the part before the first `:` — so an
|
|
273
|
+
* aggregation by class stays possible without destroying the detail.
|
|
274
|
+
*
|
|
275
|
+
* #1392, the defect this shape replaces: `reason` used to BE the head token
|
|
276
|
+
* (`String(res?.reason ?? 'unknown').split(':')[0]`). All five sandbox
|
|
277
|
+
* refusals `scripts/lib/telemetry/sync.mjs` produces (`sandbox:temp-root`,
|
|
278
|
+
* `sandbox:probe-failed`, …) were therefore written as bare `sandbox`, while
|
|
279
|
+
* the one reader — `scripts/lib/telemetry-flush-health-banner.mjs` — requires
|
|
280
|
+
* `reason.startsWith('sandbox:')`. The banner could not fire on any record this
|
|
281
|
+
* emitter ever wrote, and a mute instrument looks exactly like a healthy
|
|
282
|
+
* channel (HR-105). The cut is irrecoverable at the reader, so the fix belongs
|
|
283
|
+
* here: persist the full reason, carry the class beside it.
|
|
263
284
|
*
|
|
264
285
|
* @param {{sent?: boolean, queued?: boolean, reason?: string}|null|undefined} res
|
|
265
|
-
* @returns {{outcome: 'sent'|'queued'|'gated'|'skipped', reason: string}}
|
|
286
|
+
* @returns {{outcome: 'sent'|'queued'|'gated'|'skipped', reason: string, reason_class: string}}
|
|
266
287
|
*/
|
|
267
|
-
function classifyFlush(res) {
|
|
268
|
-
const reason = String(res?.reason ?? 'unknown').
|
|
269
|
-
|
|
270
|
-
if (res?.
|
|
271
|
-
if (
|
|
272
|
-
return { outcome: '
|
|
288
|
+
export function classifyFlush(res) {
|
|
289
|
+
const reason = String(res?.reason ?? 'unknown').slice(0, FLUSH_REASON_MAX_CHARS);
|
|
290
|
+
const reasonClass = reason.split(':')[0];
|
|
291
|
+
if (res?.sent === true) return { outcome: 'sent', reason, reason_class: reasonClass };
|
|
292
|
+
if (res?.queued === true) return { outcome: 'queued', reason, reason_class: reasonClass };
|
|
293
|
+
if (reasonClass === 'gated') return { outcome: 'gated', reason, reason_class: reasonClass };
|
|
294
|
+
return { outcome: 'skipped', reason, reason_class: reasonClass };
|
|
273
295
|
}
|
|
274
296
|
|
|
275
297
|
/**
|
|
@@ -287,8 +309,9 @@ function classifyFlush(res) {
|
|
|
287
309
|
* statement) — this function deliberately does not re-implement it, so there is
|
|
288
310
|
* exactly one place where "may we send?" is decided.
|
|
289
311
|
*
|
|
290
|
-
* Always emits `orchestrator.telemetry.flush` with `{ outcome, reason
|
|
291
|
-
* NOTHING else — no payload, no anon_id. Per
|
|
312
|
+
* Always emits `orchestrator.telemetry.flush` with `{ outcome, reason,
|
|
313
|
+
* reason_class }` and NOTHING else — no payload, no anon_id. Per
|
|
314
|
+
* `.claude/rules/host-resources.md`
|
|
292
315
|
* HR-105, a mechanism whose firing rate nothing records cannot be falsified;
|
|
293
316
|
* this event is what makes the flush rate measurable next time.
|
|
294
317
|
*
|
|
@@ -305,13 +328,17 @@ async function flushTelemetry(projectRoot) {
|
|
|
305
328
|
}))
|
|
306
329
|
// `persistence: false` means this session leaves no durable local trace;
|
|
307
330
|
// a telemetry ping is a durable record too, so it honours the same switch.
|
|
308
|
-
|
|
331
|
+
// `reason_class` is spelled out on BOTH non-flush() branches so every
|
|
332
|
+
// record of this event carries the same three keys — a consumer grouping
|
|
333
|
+
// by class must not have to treat "absent" as a fourth class.
|
|
334
|
+
: { outcome: 'skipped', reason: 'persistence-disabled', reason_class: 'persistence-disabled' };
|
|
309
335
|
} catch {
|
|
310
|
-
result = { outcome: 'skipped', reason: 'error' };
|
|
336
|
+
result = { outcome: 'skipped', reason: 'error', reason_class: 'error' };
|
|
311
337
|
}
|
|
312
338
|
|
|
313
|
-
// `result` is passed through verbatim: it holds EXACTLY
|
|
314
|
-
// no payload field can leak into the
|
|
339
|
+
// `result` is passed through verbatim: it holds EXACTLY
|
|
340
|
+
// {outcome, reason, reason_class}, so no payload field can leak into the
|
|
341
|
+
// event by accident.
|
|
315
342
|
try {
|
|
316
343
|
await emitEvent('orchestrator.telemetry.flush', result);
|
|
317
344
|
} catch { /* observability is best-effort */ }
|
|
@@ -618,8 +645,20 @@ async function main() {
|
|
|
618
645
|
// Dead-by-age relaxation (relaxDeadByAge/assumeDeadBeforeMs, #731 — used by the
|
|
619
646
|
// historical migration CLI) is NEVER passed here: a foreign lock that is live
|
|
620
647
|
// at hook-time is, by definition, a real active session, not stale history.
|
|
648
|
+
// `ownSessionIsEnding` (#1376) rides the SAME `isRecordedSession`
|
|
649
|
+
// attestation as `duration_ms` / `semantic_session_id` above, for the
|
|
650
|
+
// same reason: it is a claim made ON BEHALF of the recorded session, and
|
|
651
|
+
// a foreign terminating window that inherited this repo's
|
|
652
|
+
// `current-session.json` identity (#863 defect (b)) must never make it.
|
|
653
|
+
// With the attestation false the core keeps its blanket own-live-lock
|
|
654
|
+
// skip, so the #863 behaviour is preserved exactly where #863 applied.
|
|
621
655
|
try {
|
|
622
|
-
const res = await backfillAbandonedSession({
|
|
656
|
+
const res = await backfillAbandonedSession({
|
|
657
|
+
repoRoot: projectRoot,
|
|
658
|
+
sessionId,
|
|
659
|
+
semanticSessionId,
|
|
660
|
+
ownSessionIsEnding: isRecordedSession,
|
|
661
|
+
});
|
|
623
662
|
await emitBackfillOutcome('abandoned', res, { sessionId, semanticSessionId });
|
|
624
663
|
} catch { /* best-effort — never block teardown */ }
|
|
625
664
|
|
|
@@ -627,7 +666,7 @@ async function main() {
|
|
|
627
666
|
// above and to THIS session's own id: it reads STATE.md directly and
|
|
628
667
|
// repairs a PAST session whose `status: completed` was set (by hand or
|
|
629
668
|
// otherwise) without session-end's Phase 3.7 ever writing the matching
|
|
630
|
-
// sessions.jsonl record — the exact state `
|
|
669
|
+
// sessions.jsonl record — the exact state `skills/close/SKILL.md`'s
|
|
631
670
|
// Pre-Check then reads as "already finalized" forever after. Cheap
|
|
632
671
|
// no-op on the overwhelmingly common path (STATE.md status is
|
|
633
672
|
// 'active'/'paused'/'idle', or the record already exists).
|
|
@@ -818,6 +857,14 @@ async function main() {
|
|
|
818
857
|
}
|
|
819
858
|
|
|
820
859
|
// Exit 0 always — informational hook must never block session teardown.
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
860
|
+
// Entry guard (#1298 P7): run only as the node script every harness execs
|
|
861
|
+
// (`sh run-node.sh <this file>`); a bare `import()` must not tear down the
|
|
862
|
+
// live repo's session state.
|
|
863
|
+
if (isMainModule(import.meta.url)) {
|
|
864
|
+
// #1393: the profile gate sits INSIDE the entry guard — at module top level
|
|
865
|
+
// its `process.exit(0)` exited every process that merely imported this hook.
|
|
866
|
+
if (!shouldRunHook('on-session-end')) process.exit(0);
|
|
867
|
+
main()
|
|
868
|
+
.catch(() => {})
|
|
869
|
+
.finally(() => process.exit(0));
|
|
870
|
+
}
|
|
@@ -36,8 +36,6 @@ import { randomUUID } from 'node:crypto';
|
|
|
36
36
|
import path from 'node:path';
|
|
37
37
|
|
|
38
38
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
39
|
-
// #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
|
|
40
|
-
if (!shouldRunHook('on-session-start')) process.exit(0);
|
|
41
39
|
|
|
42
40
|
import { emitEvent, eventsFilePath } from '../scripts/lib/events.mjs';
|
|
43
41
|
import { maybeRotate } from '../scripts/lib/events-rotation.mjs';
|
|
@@ -54,6 +52,7 @@ import { detectColdStart, consumeMarker } from '../scripts/lib/cold-start-detect
|
|
|
54
52
|
import { parseSessionId } from '../scripts/lib/session-id.mjs';
|
|
55
53
|
import { readTelemetryState, resolveConsent, isCiEnv } from '../scripts/lib/telemetry/consent.mjs';
|
|
56
54
|
import { loadOwnerConfig } from '../scripts/lib/owner-yaml.mjs';
|
|
55
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
57
56
|
// SSOT for the "is this a re-entry into the same logical session?" question
|
|
58
57
|
// (#1091). Defined in the lock-bootstrap leaf module, which this hook already
|
|
59
58
|
// loads, so the preservation branch below and the lock force-refresh gate can
|
|
@@ -453,8 +452,10 @@ async function resolveSessionId(input, projectRoot) {
|
|
|
453
452
|
// observe. A naive full overwrite of current-session.json drops the
|
|
454
453
|
// `last_wave` / `last_batch` markers written mid-session by
|
|
455
454
|
// post-tool-batch-wave-signal.mjs, which makes the next PostToolBatch
|
|
456
|
-
// re-read last_wave as absent→0 and
|
|
457
|
-
//
|
|
455
|
+
// re-read last_wave as absent→0 and RE-OPEN wave N mid-wave, re-stamping
|
|
456
|
+
// wave_start_sha and so moving the #980 files_changed start point. (Until
|
|
457
|
+
// 2026-09-19 it also re-emitted a duplicate wave.started{N}; that event
|
|
458
|
+
// has since been removed.)
|
|
458
459
|
//
|
|
459
460
|
// Three cases, exactly one of which preserves nothing:
|
|
460
461
|
// 'raw-id' — same-logical-session source AND the recorded raw
|
|
@@ -764,8 +765,12 @@ async function main() {
|
|
|
764
765
|
// v3.1.0 env-aware banner (opt-out via enable-host-banner: false in Session Config).
|
|
765
766
|
// The ask-via-tool nudge rides the same opt-out flag — both are coordinator
|
|
766
767
|
// reminders shown at session start; users who silence one expect silence.
|
|
768
|
+
// Hoisted because the cold-start DISPLAY below rides the same opt-out (#1133)
|
|
769
|
+
// and `bannerData` is not a usable proxy for it: `emitHostBanner()` returns
|
|
770
|
+
// null on its own error paths, which would silently re-open the nudge.
|
|
771
|
+
const hostBannerEnabled = await isHostBannerEnabled(projectRoot);
|
|
767
772
|
let bannerData = null;
|
|
768
|
-
if (
|
|
773
|
+
if (hostBannerEnabled) {
|
|
769
774
|
bannerData = await emitHostBanner(projectRoot);
|
|
770
775
|
// Always-on nudge: a user decision has three legitimate forms and AUQ-001
|
|
771
776
|
// routes between them in order — operator verb first (nothing is blocked
|
|
@@ -795,7 +800,14 @@ async function main() {
|
|
|
795
800
|
enabled: coldStartCfg.enabled !== false,
|
|
796
801
|
});
|
|
797
802
|
if (decision.shouldEmit) {
|
|
798
|
-
|
|
803
|
+
// #1133 — the DISPLAY rides the enable-host-banner opt-out like every
|
|
804
|
+
// other start-of-session emitter ("users who silence one expect
|
|
805
|
+
// silence"). The one-shot marker consumption below does NOT: the
|
|
806
|
+
// opt-out silences the display, never the measurement or the state
|
|
807
|
+
// transition. Consuming it unconditionally is what keeps the nudge
|
|
808
|
+
// one-shot for an operator who has banners off — otherwise it re-arms
|
|
809
|
+
// on every start forever.
|
|
810
|
+
if (hostBannerEnabled) pushBanner(decision.bannerLines.join('\n'));
|
|
799
811
|
if (decision.markerPath) {
|
|
800
812
|
await consumeMarker(decision.markerPath).catch(() => false);
|
|
801
813
|
}
|
|
@@ -1219,6 +1231,14 @@ async function main() {
|
|
|
1219
1231
|
// Size-based rotation of events.jsonl (#251). Session-start is the single
|
|
1220
1232
|
// rotation trigger — per-append overhead is rejected design. Any failure
|
|
1221
1233
|
// is swallowed: rotation must NEVER block the hook.
|
|
1234
|
+
//
|
|
1235
|
+
// #1401 — the DURABLE record of a rotation is no longer this block. Until
|
|
1236
|
+
// 2026-09-19 the only evidence a rotation had happened was the `console.error`
|
|
1237
|
+
// below, and this hook's stderr is discarded by the harness: a rotation and a
|
|
1238
|
+
// DELETED archive were byte-identical from outside. `maybeRotate()` now writes
|
|
1239
|
+
// an `orchestrator.events.rotated` record as the first line of the new active
|
|
1240
|
+
// file, so the ledger carries its own break whatever happens to this stderr
|
|
1241
|
+
// line. The line stays as an operator convenience only — never as the record.
|
|
1222
1242
|
try {
|
|
1223
1243
|
let rotCfg = { enabled: true, 'max-size-mb': 10, 'max-backups': 5 };
|
|
1224
1244
|
try {
|
|
@@ -1234,7 +1254,13 @@ async function main() {
|
|
|
1234
1254
|
enabled: rotCfg.enabled !== false,
|
|
1235
1255
|
});
|
|
1236
1256
|
if (result.rotated) {
|
|
1237
|
-
|
|
1257
|
+
const pruned = result.pruned?.length ? `, pruned ${result.pruned.length}` : '';
|
|
1258
|
+
const ledger = result.recordWritten ? '' : ' [WARN: rotation record NOT written]';
|
|
1259
|
+
console.error(
|
|
1260
|
+
`events-rotation: archived ${result.archivedAs} ` +
|
|
1261
|
+
`(${result.sizeBefore} bytes, ${result.lines ?? '?'} lines, ` +
|
|
1262
|
+
`${result.firstTs ?? 'unknown'} → ${result.lastTs ?? 'unknown'}${pruned})${ledger}`,
|
|
1263
|
+
);
|
|
1238
1264
|
}
|
|
1239
1265
|
} catch (err) {
|
|
1240
1266
|
console.error(`events-rotation: skipped (${err?.message ?? err})`);
|
|
@@ -1245,7 +1271,18 @@ async function main() {
|
|
|
1245
1271
|
// flushBanner() runs here too so a throw partway through main() still surfaces
|
|
1246
1272
|
// whatever was already collected; it is idempotent, so the normal path (which
|
|
1247
1273
|
// flushes at the end of main) does not double-emit.
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1274
|
+
//
|
|
1275
|
+
// Entry guard (#1298 P7): every harness execs this file as the node script
|
|
1276
|
+
// (`sh run-node.sh <this file>` → `exec node "$@"`), so argv[1] IS this module
|
|
1277
|
+
// on every real path. A bare `import()` — a probe, a test, a curious agent —
|
|
1278
|
+
// must not run main() against the live repo: incident W4-FX1 overwrote
|
|
1279
|
+
// `.orchestrator/current-session.json` exactly that way.
|
|
1280
|
+
if (isMainModule(import.meta.url)) {
|
|
1281
|
+
// #1393: the profile gate sits INSIDE the entry guard — at module top level
|
|
1282
|
+
// its `process.exit(0)` exited every process that merely imported this hook.
|
|
1283
|
+
if (!shouldRunHook('on-session-start')) process.exit(0);
|
|
1284
|
+
main().catch(() => {}).finally(() => {
|
|
1285
|
+
flushBanner();
|
|
1286
|
+
process.exit(0);
|
|
1287
|
+
});
|
|
1288
|
+
}
|