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/on-stop.mjs
CHANGED
|
@@ -30,28 +30,28 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import path from 'node:path';
|
|
33
|
+
import { spawn } from 'node:child_process';
|
|
34
|
+
import { performance } from 'node:perf_hooks';
|
|
33
35
|
import {
|
|
34
36
|
promises as fs,
|
|
35
|
-
closeSync,
|
|
36
37
|
existsSync,
|
|
37
|
-
fstatSync,
|
|
38
|
-
openSync,
|
|
39
38
|
readFileSync,
|
|
40
|
-
readSync,
|
|
41
39
|
statSync,
|
|
42
40
|
writeFileSync,
|
|
43
41
|
} from 'node:fs';
|
|
42
|
+
import { fileURLToPath } from 'node:url';
|
|
44
43
|
|
|
45
44
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
45
|
+
import { readTailWindow } from '../scripts/lib/tail-window.mjs';
|
|
46
46
|
import { AGENT_ID_RE, resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
|
|
47
|
-
// #211: exit 0 immediately (silent allow) when this hook is disabled via profile/env
|
|
48
|
-
if (!shouldRunHook('on-stop')) process.exit(0);
|
|
49
47
|
|
|
50
48
|
import { emitEvent } from '../scripts/lib/events.mjs';
|
|
51
49
|
import { detectPlatform, getProjectDir } from '../scripts/lib/platform.mjs';
|
|
52
50
|
import { parseSessionId } from '../scripts/lib/session-id.mjs';
|
|
51
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
53
52
|
import { heartbeat, logSweepEvent } from '../scripts/lib/session-registry.mjs';
|
|
54
53
|
import { readLock, updateHeartbeat } from '../scripts/lib/session-lock.mjs';
|
|
54
|
+
import { _parseReaper } from '../scripts/lib/config/reaper.mjs';
|
|
55
55
|
|
|
56
56
|
// ---------------------------------------------------------------------------
|
|
57
57
|
// stdin reading (inline — no io.mjs because Stop hooks exit 0 always, never deny)
|
|
@@ -551,6 +551,39 @@ async function handleSubagentStop(input) {
|
|
|
551
551
|
} catch { /* probe failed — omit rather than assert `false` */ }
|
|
552
552
|
|
|
553
553
|
try {
|
|
554
|
+
// `sidecar_missing` — the DENOMINATOR for every meta-derived field below
|
|
555
|
+
// (#1289 Befund 2). `agent_type_meta` and `tool_use_id` come from the same
|
|
556
|
+
// read, so they co-occur perfectly (measured 2026-09-16 over
|
|
557
|
+
// `.orchestrator/metrics/events.jsonl`: 15.291 `orchestrator.agent.stopped`
|
|
558
|
+
// records, 542 with `agent_type_meta`, 542 with `tool_use_id`,
|
|
559
|
+
// both/only-meta/only-tool = 542/0/0). That made the low presence rate look
|
|
560
|
+
// like a producer defect in the KEY, when 1000/1000 sidecars on this host
|
|
561
|
+
// carry `agentType`. This flag makes "no sidecar found" visible in the
|
|
562
|
+
// record instead of indistinguishable from "sidecar found, field absent".
|
|
563
|
+
//
|
|
564
|
+
// WHAT THIS KEY IS NOT: a sidecar-LOOKUP failure rate. Re-measured
|
|
565
|
+
// 2026-09-16 over 15.457 `orchestrator.agent.stopped` records (5086
|
|
566
|
+
// `transcript_found:false` vs 553 `true`), the false mass is the #939/#949
|
|
567
|
+
// PHANTOM-STOP class — no subagent ever existed, so there is nothing for
|
|
568
|
+
// the lookup to find:
|
|
569
|
+
// - 0 of 5077 distinct false `agent_id`s have a sidecar ANYWHERE on this
|
|
570
|
+
// host (`find ~/.claude/projects -path '*/subagents/agent-*.jsonl'` →
|
|
571
|
+
// 8626 ids), and 0 have a `SubagentStart` record (2176 starts in
|
|
572
|
+
// `.orchestrator/metrics/subagents.jsonl`);
|
|
573
|
+
// - 0 records carrying a valid `agent_id` got a null resolver result;
|
|
574
|
+
// - the two classes are perfectly bimodal on the type field:
|
|
575
|
+
// `false` + no `agent` 5086, `true` + typed 553, off-diagonal 0.
|
|
576
|
+
// `agent` (the harness `agent_type`) is therefore the ready-made
|
|
577
|
+
// discriminator — no further key is needed. Do NOT "repair" the derivation
|
|
578
|
+
// in hooks/_lib/subagent-paths.mjs to chase this rate: it already resolves
|
|
579
|
+
// 553/553 of the real stops, and a widened one would only start resolving
|
|
580
|
+
// onto a FOREIGN agent's sidecar. This misreading has now cost three
|
|
581
|
+
// investigations (#939, the 2026-08-11 ledger re-run, #1289 Befund 2).
|
|
582
|
+
//
|
|
583
|
+
// Present ONLY when true, like every other optional key in this payload;
|
|
584
|
+
// its own probe is inside this try, so a failed `existsSync` omits it and
|
|
585
|
+
// nothing else (a telemetry fault must never change a decision).
|
|
586
|
+
if (!existsSync(metaPath)) payload.sidecar_missing = true;
|
|
554
587
|
// One small read, one parse, two fields. `description` is operator prose
|
|
555
588
|
// and is deliberately NOT carried: this payload also travels over the
|
|
556
589
|
// optional Clank webhook unredacted.
|
|
@@ -565,7 +598,16 @@ async function handleSubagentStop(input) {
|
|
|
565
598
|
payload.tool_use_id = meta.toolUseId.trim();
|
|
566
599
|
}
|
|
567
600
|
// A SECOND witness for the type — never merged into `agent`, so the
|
|
568
|
-
// empty-`agent_type` rate stays measurable.
|
|
601
|
+
// empty-`agent_type` rate stays measurable. NO FALLBACK MERGE, deliberately:
|
|
602
|
+
// `payload.agent` already carries the harness's `input.agent_type`, and
|
|
603
|
+
// filling one from the other would erase exactly the signal this pair
|
|
604
|
+
// exists to produce.
|
|
605
|
+
//
|
|
606
|
+
// REPORTING RULE: the presence rate of this key is only meaningful against
|
|
607
|
+
// the events whose SIDECAR WAS FOUND — "present on N of M events without
|
|
608
|
+
// `sidecar_missing`", never N of all `orchestrator.agent.stopped` records.
|
|
609
|
+
// Measured against all events the rate reads as a producer defect in this
|
|
610
|
+
// block; against its real denominator it is a lookup-failure rate.
|
|
569
611
|
if (typeof meta?.agentType === 'string' && AGENT_TYPE_META_RE.test(meta.agentType.trim())) {
|
|
570
612
|
payload.agent_type_meta = meta.agentType.trim();
|
|
571
613
|
}
|
|
@@ -701,17 +743,11 @@ function firstNonEmptyString(input, keys) {
|
|
|
701
743
|
* @returns {string|null}
|
|
702
744
|
*/
|
|
703
745
|
function readStatusFromTranscriptTail(transcriptPath) {
|
|
704
|
-
let fd = null;
|
|
705
746
|
try {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const length = Math.min(size, STATUS_TAIL_BYTES);
|
|
709
|
-
const start = size - length;
|
|
710
|
-
const buf = Buffer.alloc(length);
|
|
711
|
-
const read = readSync(fd, buf, 0, length, start);
|
|
712
|
-
let lines = buf.subarray(0, read).toString('utf8').split('\n');
|
|
747
|
+
const window = readTailWindow(transcriptPath, STATUS_TAIL_BYTES);
|
|
748
|
+
let lines = window.text.split('\n');
|
|
713
749
|
// Drop the leading partial line when the window did not start at byte 0.
|
|
714
|
-
if (
|
|
750
|
+
if (window.cut) lines = lines.slice(1);
|
|
715
751
|
|
|
716
752
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
717
753
|
const line = lines[i].trim();
|
|
@@ -732,10 +768,6 @@ function readStatusFromTranscriptTail(transcriptPath) {
|
|
|
732
768
|
return null;
|
|
733
769
|
} catch {
|
|
734
770
|
return null;
|
|
735
|
-
} finally {
|
|
736
|
-
if (fd !== null) {
|
|
737
|
-
try { closeSync(fd); } catch { /* best-effort */ }
|
|
738
|
-
}
|
|
739
771
|
}
|
|
740
772
|
}
|
|
741
773
|
|
|
@@ -743,10 +775,158 @@ function readStatusFromTranscriptTail(transcriptPath) {
|
|
|
743
775
|
// main
|
|
744
776
|
// ---------------------------------------------------------------------------
|
|
745
777
|
|
|
778
|
+
// ---------------------------------------------------------------------------
|
|
779
|
+
// orphan-reaper trigger (#1432 B4)
|
|
780
|
+
// ---------------------------------------------------------------------------
|
|
781
|
+
//
|
|
782
|
+
// DUPLICATED VERBATIM between the two trigger hooks —
|
|
783
|
+
// hooks/post-tool-batch-wave-signal.mjs <-> hooks/on-stop.mjs. Deliberate, with
|
|
784
|
+
// a named revisit trigger (BV-004): they are the only two trigger points the
|
|
785
|
+
// PRD declares, and a shared `hooks/_lib/reaper-trigger.mjs` would be a third
|
|
786
|
+
// file in the hook import graph. SIZE, measured 2026-09-22 by diffing the two
|
|
787
|
+
// blocks: 138 identical lines — this note said "~40 lines of glue" until then,
|
|
788
|
+
// which is what made the duplicate look cheaper than it is. Extract it the
|
|
789
|
+
// moment a THIRD hook needs the trigger, or the moment the copies must differ.
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Filesystem path of the scan CLI, spawned as a PLAIN argv call.
|
|
793
|
+
*
|
|
794
|
+
* It used to be a `file://` URL handed to `node --input-type=module -e
|
|
795
|
+
* <program>`, because `scripts/lib/orphan-reaper.mjs` had no entry guard. It
|
|
796
|
+
* has one now (`parseReaperCliArgs` + the `isMainModule` tail), so the child's
|
|
797
|
+
* contract lives in that module instead of as source text duplicated here —
|
|
798
|
+
* and nothing this hook builds is a program any more: every variable part is
|
|
799
|
+
* an argv value, which cannot become code whatever the checkout path contains.
|
|
800
|
+
*
|
|
801
|
+
* `fileURLToPath`, not `new URL(...).pathname`: the latter leaves a
|
|
802
|
+
* percent-encoded path for any checkout directory containing a space.
|
|
803
|
+
*/
|
|
804
|
+
const ORPHAN_REAPER_SCRIPT = fileURLToPath(
|
|
805
|
+
new URL('../scripts/lib/orphan-reaper.mjs', import.meta.url),
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Read the `reaper:` block from the project's Session Config host file.
|
|
810
|
+
* Sync + inline, mirroring `hooks/loop-guard.mjs` `loadConfig()` — a hot hook
|
|
811
|
+
* path must not import the full config orchestrator. A missing or unreadable
|
|
812
|
+
* file yields the parser defaults, i.e. DISABLED.
|
|
813
|
+
*
|
|
814
|
+
* @param {string} projectDir
|
|
815
|
+
* @returns {ReturnType<typeof _parseReaper>}
|
|
816
|
+
*/
|
|
817
|
+
function loadReaperConfig(projectDir) {
|
|
818
|
+
for (const name of ['CLAUDE.md', 'AGENTS.md']) {
|
|
819
|
+
try {
|
|
820
|
+
return _parseReaper(readFileSync(path.join(projectDir, name), 'utf8'));
|
|
821
|
+
} catch {
|
|
822
|
+
// missing or unreadable — try the next candidate
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return _parseReaper('');
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Trigger the orphan scan, throttled and NON-BLOCKING (PRD FA4).
|
|
830
|
+
*
|
|
831
|
+
* The hook itself does exactly two pieces of I/O — one config read and one
|
|
832
|
+
* `stat` of the throttle marker — and then hands the work to a DETACHED,
|
|
833
|
+
* unref'd child. The scan is never run inline: one `ps` round-trip out of Node
|
|
834
|
+
* was measured at ~47 ms over 287 KB of output, which alone would blow the
|
|
835
|
+
* 50 ms `reaper.max-hook-latency-ms` budget this hook has to stay inside.
|
|
836
|
+
*
|
|
837
|
+
* The marker is stamped BEFORE the spawn, so a spawn that fails still consumes
|
|
838
|
+
* the throttle window — otherwise a broken spawn would be retried on every
|
|
839
|
+
* single tool batch.
|
|
840
|
+
*
|
|
841
|
+
* `reaper.max-hook-latency-ms` is the ceiling on the work above — measured here
|
|
842
|
+
* with `performance.now()` and reported as ONE stderr WARN line when exceeded.
|
|
843
|
+
* A WARN and not an event: this fires from a `PostToolBatch`-class hook, and a
|
|
844
|
+
* per-fire telemetry record is exactly the always-on signal
|
|
845
|
+
* `.claude/rules/host-resources.md` HR-101 calls a broken instrument. The
|
|
846
|
+
* measurement covers preparation only — the scan itself runs detached, which is
|
|
847
|
+
* the whole reason the budget can be held.
|
|
848
|
+
*
|
|
849
|
+
* Never throws: any failure degrades silently (PRD FA4 "lautlos degradieren").
|
|
850
|
+
*
|
|
851
|
+
* @param {object} [opts]
|
|
852
|
+
* @param {string} [opts.projectDir] Repo root; defaults to `getProjectDir()`.
|
|
853
|
+
* @param {number} [opts.now] Injected clock (ms).
|
|
854
|
+
* @param {Function} [opts.spawnFn] Injected `spawn` (tests).
|
|
855
|
+
* @param {Function} [opts.statFn] Injected `statSync` (tests).
|
|
856
|
+
* @param {Function} [opts.writeFn] Injected marker writer (tests).
|
|
857
|
+
* @param {() => number} [opts.clockFn] Injected monotonic clock for the latency
|
|
858
|
+
* budget (tests); defaults to `performance.now`.
|
|
859
|
+
* @returns {Promise<{spawned: boolean, reason: string}>}
|
|
860
|
+
*/
|
|
861
|
+
export async function maybeTriggerOrphanScan({
|
|
862
|
+
projectDir,
|
|
863
|
+
now,
|
|
864
|
+
spawnFn = spawn,
|
|
865
|
+
statFn,
|
|
866
|
+
writeFn,
|
|
867
|
+
clockFn = () => performance.now(),
|
|
868
|
+
} = {}) {
|
|
869
|
+
const startedAt = clockFn();
|
|
870
|
+
/** One WARN line when the preparation overran `reaper.max-hook-latency-ms`. */
|
|
871
|
+
const checkLatency = (budgetMs) => {
|
|
872
|
+
const elapsed = clockFn() - startedAt;
|
|
873
|
+
if (Number.isFinite(budgetMs) && budgetMs > 0 && elapsed > budgetMs) {
|
|
874
|
+
process.stderr.write(
|
|
875
|
+
`orphan-reaper trigger: hook latency ${elapsed.toFixed(1)} ms exceeded `
|
|
876
|
+
+ `reaper.max-hook-latency-ms (${budgetMs} ms)\n`,
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
try {
|
|
881
|
+
const root = typeof projectDir === 'string' && projectDir ? projectDir : getProjectDir();
|
|
882
|
+
const cfg = loadReaperConfig(root);
|
|
883
|
+
// Cheapest gate first: disabled means no stat, no spawn, no module load.
|
|
884
|
+
if (cfg.enabled !== true) return { spawned: false, reason: 'disabled' };
|
|
885
|
+
|
|
886
|
+
const reaper = await import('../scripts/lib/orphan-reaper.mjs');
|
|
887
|
+
const markerPath = reaper.scanMarkerPath(root);
|
|
888
|
+
const nowMs = typeof now === 'number' ? now : Date.now();
|
|
889
|
+
|
|
890
|
+
if (!reaper.shouldScanNow(markerPath, nowMs, cfg['min-scan-interval-seconds'], { statFn })) {
|
|
891
|
+
checkLatency(cfg['max-hook-latency-ms']);
|
|
892
|
+
return { spawned: false, reason: 'throttled' };
|
|
893
|
+
}
|
|
894
|
+
reaper.touchScanMarker(markerPath, { writeFn });
|
|
895
|
+
|
|
896
|
+
const child = spawnFn(
|
|
897
|
+
process.execPath,
|
|
898
|
+
[
|
|
899
|
+
ORPHAN_REAPER_SCRIPT,
|
|
900
|
+
'--repo-root', root,
|
|
901
|
+
'--mode', cfg.mode,
|
|
902
|
+
'--min-age-seconds', String(cfg['min-age-seconds']),
|
|
903
|
+
'--kill-grace-ms', String(cfg['kill-grace-ms']),
|
|
904
|
+
'--verify-wait-ms', String(cfg['verify-wait-ms']),
|
|
905
|
+
'--false-alarm-window', String(cfg['false-alarm-window']),
|
|
906
|
+
],
|
|
907
|
+
{ detached: true, stdio: 'ignore' },
|
|
908
|
+
);
|
|
909
|
+
if (child && typeof child.unref === 'function') child.unref();
|
|
910
|
+
checkLatency(cfg['max-hook-latency-ms']);
|
|
911
|
+
return { spawned: true, reason: 'spawned' };
|
|
912
|
+
} catch {
|
|
913
|
+
return { spawned: false, reason: 'error' };
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
746
917
|
async function main() {
|
|
747
918
|
const input = await readStdinJson();
|
|
748
919
|
const eventType = discriminate(input);
|
|
749
920
|
|
|
921
|
+
// Orphan-reaper trigger (#1432 B4) — SubagentStop is the PRD's second trigger
|
|
922
|
+
// point: it lands exactly when gate children have just finished and their PIDs
|
|
923
|
+
// are still known. Placed BEFORE the branch dispatch (unlike the PostToolBatch
|
|
924
|
+
// hook, which triggers last) because both branches below may throw — a
|
|
925
|
+
// `handleStop` failure must not silently disarm the watchdog. The cost is one
|
|
926
|
+
// config read plus one stat, bounded by `reaper.max-hook-latency-ms` (50 ms),
|
|
927
|
+
// and the scan itself runs in a detached child.
|
|
928
|
+
await maybeTriggerOrphanScan();
|
|
929
|
+
|
|
750
930
|
if (eventType === 'subagent_stop') {
|
|
751
931
|
const additionalContext = await handleSubagentStop(input);
|
|
752
932
|
// v2.1.163+: emit hookSpecificOutput for SubagentStop path.
|
|
@@ -818,6 +998,14 @@ function buildTerminalSequenceJson(platform) {
|
|
|
818
998
|
}
|
|
819
999
|
|
|
820
1000
|
// Exit 0 always — informational hook must never block Claude.
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1001
|
+
// Entry guard (#1298 P7): run only as the node script every harness execs
|
|
1002
|
+
// (`sh run-node.sh <this file>`); a bare `import()` must not run the Stop
|
|
1003
|
+
// handler against the live repo.
|
|
1004
|
+
if (isMainModule(import.meta.url)) {
|
|
1005
|
+
// #1393: the profile gate sits INSIDE the entry guard — at module top level
|
|
1006
|
+
// its `process.exit(0)` exited every process that merely imported this hook.
|
|
1007
|
+
if (!shouldRunHook('on-stop')) process.exit(0);
|
|
1008
|
+
main()
|
|
1009
|
+
.catch(() => {})
|
|
1010
|
+
.finally(() => process.exit(0));
|
|
1011
|
+
}
|
package/hooks/operator-steer.mjs
CHANGED
|
@@ -34,8 +34,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
34
34
|
import path from 'node:path';
|
|
35
35
|
|
|
36
36
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
37
|
-
|
|
38
|
-
if (!shouldRunHook('operator-steer')) process.exit(0);
|
|
37
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
39
38
|
|
|
40
39
|
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
41
40
|
|
|
@@ -61,4 +60,13 @@ async function main() {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
// Exit 0 always — informational hook must never block Claude.
|
|
64
|
-
|
|
63
|
+
// Entry guard (#1393): run only when this file IS the script node was invoked
|
|
64
|
+
// with — every harness path execs it (`sh run-node.sh <this file>`). A bare
|
|
65
|
+
// `import()` (a probe, a test, a curious agent) must neither run main() nor
|
|
66
|
+
// tear the importing process down. The profile gate sits INSIDE the guard for
|
|
67
|
+
// that second reason: at module top level its `process.exit(0)` exited every
|
|
68
|
+
// process that merely imported this hook.
|
|
69
|
+
if (isMainModule(import.meta.url)) {
|
|
70
|
+
if (!shouldRunHook('operator-steer')) process.exit(0);
|
|
71
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
|
72
|
+
}
|
|
@@ -68,12 +68,13 @@ import {
|
|
|
68
68
|
} from '../scripts/lib/issue-budget.mjs';
|
|
69
69
|
|
|
70
70
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
71
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
71
72
|
import { existsSync } from 'node:fs';
|
|
72
73
|
import path from 'node:path';
|
|
73
74
|
|
|
74
75
|
// Opt-out per session via SO_DISABLED_HOOKS=post-bash-issue-budget-refund; the
|
|
75
|
-
// "minimal"/"off" profiles disable it like every other non-core hook
|
|
76
|
-
|
|
76
|
+
// "minimal"/"off" profiles disable it like every other non-core hook — the gate
|
|
77
|
+
// itself runs inside the entry guard at the bottom of this file (#1393).
|
|
77
78
|
|
|
78
79
|
// ---------------------------------------------------------------------------
|
|
79
80
|
// Helpers
|
|
@@ -367,9 +368,18 @@ async function main() {
|
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
// Top-level error handler — fail open, same posture as the sibling hooks.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
// Entry guard (#1393): run only when this file IS the script node was invoked
|
|
372
|
+
// with — every harness path execs it (`sh run-node.sh <this file>`). A bare
|
|
373
|
+
// `import()` (a probe, a test, a curious agent) must neither run main() nor
|
|
374
|
+
// tear the importing process down. The profile gate sits INSIDE the guard for
|
|
375
|
+
// that second reason: at module top level its `process.exit(0)` exited every
|
|
376
|
+
// process that merely imported this hook.
|
|
377
|
+
if (isMainModule(import.meta.url)) {
|
|
378
|
+
if (!shouldRunHook('post-bash-issue-budget-refund')) process.exit(0);
|
|
379
|
+
main().catch((e) => {
|
|
380
|
+
process.stderr.write(
|
|
381
|
+
`⚠ post-bash-issue-budget-refund: internal error — ${e?.message || e}\n`,
|
|
382
|
+
);
|
|
383
|
+
process.exit(0);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
@@ -143,8 +143,6 @@
|
|
|
143
143
|
*/
|
|
144
144
|
|
|
145
145
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
146
|
-
// Exit 0 immediately (silent no-op) when disabled via profile/env (#211).
|
|
147
|
-
if (!shouldRunHook('post-bash-write-verify')) process.exit(0);
|
|
148
146
|
|
|
149
147
|
import path from 'node:path';
|
|
150
148
|
import os from 'node:os';
|
|
@@ -1120,6 +1118,9 @@ async function main() {
|
|
|
1120
1118
|
// string compare then reads false and silently no-ops the whole scope-detector
|
|
1121
1119
|
// under a symlinked `$CLAUDE_PLUGIN_ROOT`. realpath'ing both sides also survives
|
|
1122
1120
|
// `--preserve-symlinks` (where import.meta.url stays symlinked instead).
|
|
1121
|
+
// BV-004 ceiling: kept inline — hooks avoid importing scripts/lib on the hot
|
|
1122
|
+
// path; the canonical predicate is scripts/lib/is-main-module.mjs. Revisit if the
|
|
1123
|
+
// hook-import-set (hooks/_lib/hook-import-set.json) ever admits scripts/lib here.
|
|
1123
1124
|
function invokedAsScript() {
|
|
1124
1125
|
const entry = process.argv[1];
|
|
1125
1126
|
if (!entry) return false;
|
|
@@ -1133,6 +1134,9 @@ function invokedAsScript() {
|
|
|
1133
1134
|
}
|
|
1134
1135
|
const isMain = invokedAsScript();
|
|
1135
1136
|
if (isMain) {
|
|
1137
|
+
// #1393: the profile gate sits INSIDE the entry guard — at module top level
|
|
1138
|
+
// its `process.exit(0)` exited every process that merely imported this hook.
|
|
1139
|
+
if (!shouldRunHook('post-bash-write-verify')) process.exit(0);
|
|
1136
1140
|
// Advisory hook: never block, never surface an error to the tool call.
|
|
1137
1141
|
main().catch(() => {}).finally(() => process.exit(0));
|
|
1138
1142
|
}
|
|
@@ -37,8 +37,7 @@
|
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
39
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
40
|
-
|
|
41
|
-
if (!shouldRunHook('post-edit-import-probe')) process.exit(0);
|
|
40
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
42
41
|
|
|
43
42
|
import path from 'node:path';
|
|
44
43
|
import { readFileSync, existsSync } from 'node:fs';
|
|
@@ -335,10 +334,19 @@ async function main() {
|
|
|
335
334
|
// Exit 0 always — a hook that guards against hook breakage must never block.
|
|
336
335
|
// The catch stays silent towards the TOOL CALL but not towards the operator:
|
|
337
336
|
// a probe whose own breakage is invisible is a probe nobody can trust.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
337
|
+
// Entry guard (#1393): run only when this file IS the script node was invoked
|
|
338
|
+
// with — every harness path execs it (`sh run-node.sh <this file>`). A bare
|
|
339
|
+
// `import()` (a probe, a test, a curious agent) must neither run main() nor
|
|
340
|
+
// tear the importing process down. The profile gate sits INSIDE the guard for
|
|
341
|
+
// that second reason: at module top level its `process.exit(0)` exited every
|
|
342
|
+
// process that merely imported this hook.
|
|
343
|
+
if (isMainModule(import.meta.url)) {
|
|
344
|
+
if (!shouldRunHook('post-edit-import-probe')) process.exit(0);
|
|
345
|
+
main()
|
|
346
|
+
.catch((err) => {
|
|
347
|
+
try {
|
|
348
|
+
process.stderr.write(`post-edit-import-probe: ${err?.message ?? String(err)}\n`);
|
|
349
|
+
} catch { /* stderr gone — nothing left to report to */ }
|
|
350
|
+
})
|
|
351
|
+
.finally(() => process.exit(0));
|
|
352
|
+
}
|
|
@@ -32,8 +32,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
32
32
|
import { performance } from 'node:perf_hooks';
|
|
33
33
|
|
|
34
34
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
35
|
-
|
|
36
|
-
if (!shouldRunHook('post-edit-validate')) process.exit(0);
|
|
35
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
37
36
|
|
|
38
37
|
import { readStdin } from '../scripts/lib/io.mjs';
|
|
39
38
|
import { resolveProjectDir } from '../scripts/lib/platform.mjs';
|
|
@@ -220,6 +219,15 @@ async function main() {
|
|
|
220
219
|
}
|
|
221
220
|
|
|
222
221
|
// REQ-01: top-level catch — PostToolUse must never block; always exit 0
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
// Entry guard (#1393): run only when this file IS the script node was invoked
|
|
223
|
+
// with — every harness path execs it (`sh run-node.sh <this file>`). A bare
|
|
224
|
+
// `import()` (a probe, a test, a curious agent) must neither run main() nor
|
|
225
|
+
// tear the importing process down. The profile gate sits INSIDE the guard for
|
|
226
|
+
// that second reason: at module top level its `process.exit(0)` exited every
|
|
227
|
+
// process that merely imported this hook.
|
|
228
|
+
if (isMainModule(import.meta.url)) {
|
|
229
|
+
if (!shouldRunHook('post-edit-validate')) process.exit(0);
|
|
230
|
+
main().catch(() => {
|
|
231
|
+
process.exit(0);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -22,7 +22,24 @@
|
|
|
22
22
|
* `findViolations()` — the whole matcher (claim patterns, negative-context
|
|
23
23
|
* guards, evidence proximity, normalisation, dedup) lives there so it can
|
|
24
24
|
* be measured against a claim corpus without spawning this hook. It
|
|
25
|
-
* returns DEDUPLICATED `{claim, normalized, occurrences}`
|
|
25
|
+
* returns DEDUPLICATED `{claim, normalized, occurrences, kind, detail?}`
|
|
26
|
+
* records in THREE classes: `distributional` (#567/#908 — "4 of 4
|
|
27
|
+
* callers"), `gate-verdict` (w4-1 — "STATUS: done" / "alles grün" with
|
|
28
|
+
* no run receipt anywhere in the report), and `claim-mismatch` (#1385 R1
|
|
29
|
+
* — a claimed test count that NO observed vitest run carries). `kind`
|
|
30
|
+
* reaches both the ledger record and the WARN; its ABSENCE on a record
|
|
31
|
+
* means `distributional`, since no pre-w4-1 record could be anything
|
|
32
|
+
* else.
|
|
33
|
+
* 5b. The `claim-mismatch` class needs the OTHER side of the transcript, so
|
|
34
|
+
* `readTranscriptObservations()` reads the vitest summaries out of the
|
|
35
|
+
* `tool_result` blocks in the same window and passes them in. It closes
|
|
36
|
+
* an INVERSION: `RUN_RECEIPT_RE` is report-wide and matches
|
|
37
|
+
* `\d+\s+passed`, so before R1 an INVENTED number was its own receipt
|
|
38
|
+
* while an honest report with no number was flagged (measured
|
|
39
|
+
* 2026-09-19: `'STATUS: done\nTests pass: 5129 passed / 0 failed.'` → no
|
|
40
|
+
* violation; `'STATUS: done\nAlles grün.'` → two). No observations in
|
|
41
|
+
* the window ⇒ no `claim-mismatch` finding: evidence ABSENCE is
|
|
42
|
+
* `gate-verdict`'s job.
|
|
26
43
|
* 6. Attribute the claim: `agent` + `agent_source` (`payload`|`meta`|`none`)
|
|
27
44
|
* + `agent_description`, and — on `none` — the sorted stdin `payload_keys`
|
|
28
45
|
* the harness DID send, so a gap is diagnosable from the ledger.
|
|
@@ -62,8 +79,7 @@
|
|
|
62
79
|
*/
|
|
63
80
|
|
|
64
81
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
65
|
-
|
|
66
|
-
if (!shouldRunHook('post-subagent-discovery-validator')) process.exit(0);
|
|
82
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
67
83
|
|
|
68
84
|
import { createHash } from 'node:crypto';
|
|
69
85
|
import { promises as fs } from 'node:fs';
|
|
@@ -71,7 +87,11 @@ import { tmpdir } from 'node:os';
|
|
|
71
87
|
import path from 'node:path';
|
|
72
88
|
|
|
73
89
|
import { resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
|
|
74
|
-
import {
|
|
90
|
+
import {
|
|
91
|
+
findViolations,
|
|
92
|
+
readTranscriptObservations,
|
|
93
|
+
readTranscriptTail,
|
|
94
|
+
} from './_lib/subagent-transcript.mjs';
|
|
75
95
|
import { appendJsonl } from '../scripts/lib/common.mjs';
|
|
76
96
|
import { eventsFilePath } from '../scripts/lib/events.mjs';
|
|
77
97
|
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
@@ -393,7 +413,11 @@ async function main() {
|
|
|
393
413
|
if (sidecar === null) return;
|
|
394
414
|
|
|
395
415
|
const text = await readTranscriptTail(sidecar.transcript);
|
|
396
|
-
|
|
416
|
+
// The `tool_result` side of the SAME window (#1385 R1) — what the agent
|
|
417
|
+
// actually RAN, against which its claimed test counts are compared. An
|
|
418
|
+
// empty list disables the `claim-mismatch` class; it never creates one.
|
|
419
|
+
const observations = await readTranscriptObservations(sidecar.transcript);
|
|
420
|
+
const { violations, undatedVerified } = findViolations(text, { observations });
|
|
397
421
|
if (violations.length === 0) return;
|
|
398
422
|
|
|
399
423
|
const attribution = await resolveAgentAttribution(input, sidecar.meta);
|
|
@@ -443,7 +467,15 @@ async function main() {
|
|
|
443
467
|
}
|
|
444
468
|
if (seen) continue;
|
|
445
469
|
}
|
|
446
|
-
|
|
470
|
+
// AWAITED **and** CAUGHT. A telemetry write added to a guard silently
|
|
471
|
+
// disarms it unless both hold: without the await the process can exit
|
|
472
|
+
// before the append lands, and without the catch one failed write aborts
|
|
473
|
+
// the remaining violations AND the stderr WARN below — the coordinator's
|
|
474
|
+
// only copy of the finding. This hook never denies, so the failure is a
|
|
475
|
+
// lost finding rather than a lost block; it is still a loss, and a full
|
|
476
|
+
// disk must not be able to silence the scanner.
|
|
477
|
+
try {
|
|
478
|
+
await appendJsonl(filePath, {
|
|
447
479
|
event: 'discovery_validator_violation',
|
|
448
480
|
timestamp: new Date().toISOString(),
|
|
449
481
|
agent,
|
|
@@ -457,8 +489,25 @@ async function main() {
|
|
|
457
489
|
...(sessionId !== null ? { session_id: sessionId } : {}),
|
|
458
490
|
claim_text: violation.claim,
|
|
459
491
|
occurrences: violation.occurrences,
|
|
460
|
-
|
|
461
|
-
|
|
492
|
+
// Claim CLASS (w4-1, #1385 R1): `distributional` (the #567/#908
|
|
493
|
+
// patterns), `gate-verdict` (a done/green assertion with no run
|
|
494
|
+
// receipt), or `claim-mismatch` (a test count no observed run carries).
|
|
495
|
+
// A new field rather than an overloaded old one — a ledger reader
|
|
496
|
+
// triaging 3,000 records must be able to separate them without
|
|
497
|
+
// re-parsing `claim_text`, and every pre-w4-1 record is
|
|
498
|
+
// `distributional` by construction (the field's absence means that).
|
|
499
|
+
kind: violation.kind ?? 'distributional',
|
|
500
|
+
// `claim-mismatch` only: `{mismatch, claimed, observed, observed_n}`.
|
|
501
|
+
// Numbers, never the command text that produced them (precedent
|
|
502
|
+
// `8f15f77b` — `command_hash` instead of the raw command); `observed`
|
|
503
|
+
// is capped at the last 3 runs by findViolations().
|
|
504
|
+
...(violation.detail ?? {}),
|
|
505
|
+
});
|
|
506
|
+
written++;
|
|
507
|
+
} catch {
|
|
508
|
+
// Write failed — keep going. The WARN below still reaches the
|
|
509
|
+
// coordinator, which is the channel PSA-006 actually depends on.
|
|
510
|
+
}
|
|
462
511
|
}
|
|
463
512
|
|
|
464
513
|
// Advisory only (#908 item 4) — never promoted to a violation in v1.
|
|
@@ -473,10 +522,39 @@ async function main() {
|
|
|
473
522
|
? ` ${violations.length - written} already recorded earlier in this session.`
|
|
474
523
|
: '';
|
|
475
524
|
|
|
525
|
+
// THREE claim CLASSES, counted separately in the WARN (w4-1, #1385 R1):
|
|
526
|
+
// they fail different rules and want different corrections — a
|
|
527
|
+
// distributional claim needs its grep transcript, a gate verdict needs a
|
|
528
|
+
// run receipt, and a count mismatch needs the run repeated or the number
|
|
529
|
+
// corrected. One aggregate count would leave the coordinator guessing which.
|
|
530
|
+
const gateCount = violations.filter((v) => v.kind === 'gate-verdict').length;
|
|
531
|
+
const mismatchCount = violations.filter((v) => v.kind === 'claim-mismatch').length;
|
|
532
|
+
const distCount = violations.length - gateCount - mismatchCount;
|
|
533
|
+
const classNote = gateCount === 0
|
|
534
|
+
? ''
|
|
535
|
+
: ` ${gateCount} of them ${gateCount === 1 ? 'is a' : 'are'} DONE/GATE verdict(s) with no run ` +
|
|
536
|
+
`receipt (no test count, no exit code) anywhere in the report; ` +
|
|
537
|
+
`${distCount} repo-state/distributional.`;
|
|
538
|
+
|
|
539
|
+
// The correction this class asks for is named, because it is NOT the one
|
|
540
|
+
// the other two ask for: no amount of added grep evidence fixes a number
|
|
541
|
+
// that contradicts every run in the window.
|
|
542
|
+
const mismatchNote = mismatchCount === 0
|
|
543
|
+
? ''
|
|
544
|
+
: ` ${mismatchCount} ${mismatchCount === 1 ? 'is a' : 'are'} COUNT MISMATCH: ` +
|
|
545
|
+
violations
|
|
546
|
+
.filter((v) => v.kind === 'claim-mismatch')
|
|
547
|
+
.map((v) => {
|
|
548
|
+
const observed = (v.detail?.observed ?? []).map((o) => `${o.passed} passed`).join(', ');
|
|
549
|
+
return `claimed ${v.detail?.claimed?.passed} passed, observed ${observed || 'none'}`;
|
|
550
|
+
})
|
|
551
|
+
.join('; ') +
|
|
552
|
+
` — re-run the suite or correct the number.`;
|
|
553
|
+
|
|
476
554
|
const warnText =
|
|
477
|
-
`⚠ PSA-006: ${violations.length} distinct
|
|
478
|
-
`"${agent}" (source: ${attribution.source}) lack
|
|
479
|
-
`(grep/rg/find/git/wc/jq/ls/node/npm) (non-blocking)
|
|
555
|
+
`⚠ PSA-006: ${violations.length} distinct claim(s) from agent ` +
|
|
556
|
+
`"${agent}" (source: ${attribution.source}) lack measurement evidence ` +
|
|
557
|
+
`(grep/rg/find/git/wc/jq/ls/node/npm) (non-blocking).${classNote}${mismatchNote}` +
|
|
480
558
|
`${suppressedNote}${undatedNote} ` +
|
|
481
559
|
`See .claude/rules/parallel-sessions.md § PSA-006.`;
|
|
482
560
|
process.stderr.write(warnText + '\n');
|
|
@@ -526,5 +604,12 @@ async function main() {
|
|
|
526
604
|
}));
|
|
527
605
|
}
|
|
528
606
|
|
|
529
|
-
//
|
|
530
|
-
|
|
607
|
+
// Entry guard (#1393): run only as the node script the harness execs — a bare
|
|
608
|
+
// `import()` must run no handler and must not exit the importing process.
|
|
609
|
+
if (isMainModule(import.meta.url)) {
|
|
610
|
+
// Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
|
|
611
|
+
if (!shouldRunHook('post-subagent-discovery-validator')) process.exit(0);
|
|
612
|
+
|
|
613
|
+
// Exit 0 always — informational hook must never block Claude (#567 v1).
|
|
614
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
|
615
|
+
}
|