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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* eval/engine.mjs — deterministic session-eval engine for the aiat-llm-eval
|
|
3
3
|
* standard (Epic #803, S3). Scores ONE completed orchestrator session against
|
|
4
|
-
* the rubric-
|
|
5
|
-
* (sessions.jsonl + events.jsonl
|
|
4
|
+
* the rubric-v2 dimensions using ONLY local metrics files
|
|
5
|
+
* (sessions.jsonl + events.jsonl AND its rotated `_archive/`, #1407 — the
|
|
6
|
+
* scored window is a PAST one and may predate the last rotation). Missing
|
|
7
|
+
* source data ⇒ `cannot-determine`
|
|
6
8
|
* with an honest reason in evidence. It NEVER guesses, and it produces NO
|
|
7
9
|
* global score (the schema forbids one by construction).
|
|
8
10
|
*
|
|
@@ -16,16 +18,31 @@
|
|
|
16
18
|
* `--verify` path checks. provenance.engine_commit / harness.hostname_hash may
|
|
17
19
|
* vary across machines/commits but are EXCLUDED from the per-dimension diff.
|
|
18
20
|
*
|
|
19
|
-
* ── rubric-
|
|
21
|
+
* ── rubric-v2 DIMENSIONS (pre-registered verbatim in skills/eval/rubric-v2.md) ─
|
|
20
22
|
*
|
|
21
23
|
* verification-evidence quality_gate events in the (clean) window all green
|
|
22
24
|
* plan-fidelity effectiveness.completion_rate vs the v1 threshold
|
|
23
25
|
* gate-health the last full-gate event in the (clean) window
|
|
24
|
-
* process-safety
|
|
26
|
+
* process-safety agent_summary.spiral ONLY (adverse signals)
|
|
27
|
+
* guard-friction blocked / warned / loop.warning — REPORTED, never graded
|
|
25
28
|
* efficiency-kpis REPORTED, never graded (status always not-applicable)
|
|
26
29
|
*
|
|
27
30
|
* Each scorer emits { id, method:'deterministic', status, evidence, score? }
|
|
28
31
|
* where status ∈ pass | fail | not-applicable | cannot-determine.
|
|
32
|
+
*
|
|
33
|
+
* ── CROSS-VERSION READABILITY (#1037) ───────────────────────────────────────
|
|
34
|
+
*
|
|
35
|
+
* Records written before this engine carry `rubric_version: "rubric-v1"` and
|
|
36
|
+
* five dimensions; records written by it carry `"rubric-v2"` and six. Neither
|
|
37
|
+
* `schema.mjs` (rubric_version = any non-empty string, dimension id = any
|
|
38
|
+
* non-empty string) nor `report.mjs` (iterates `dimensions[]`, prints
|
|
39
|
+
* `rubric_version` verbatim) enumerates a fixed dimension set, so both shapes
|
|
40
|
+
* read and render without a crash. What does NOT survive a version change is
|
|
41
|
+
* the `--verify` REPLAY: re-scoring a stored rubric-v1 record with this engine
|
|
42
|
+
* necessarily reports drift on `process-safety` plus a `present-in-fresh-only`
|
|
43
|
+
* `guard-friction`. That is correct — the stored verdict was produced by a
|
|
44
|
+
* different pre-registered formula — but `scripts/eval-session.mjs` renders it
|
|
45
|
+
* as `DRIFT` with no version context (follow-up, see rubric-v2 § Änderungen).
|
|
29
46
|
*/
|
|
30
47
|
|
|
31
48
|
import { createHash } from 'node:crypto';
|
|
@@ -35,17 +52,17 @@ import os from 'node:os';
|
|
|
35
52
|
import path from 'node:path';
|
|
36
53
|
|
|
37
54
|
import { resolvePluginRoot } from '../common.mjs';
|
|
38
|
-
import {
|
|
55
|
+
import { readEventsWithRotations } from '../events.mjs';
|
|
39
56
|
import { readCanonicalSessions } from '../sessions-canonical.mjs';
|
|
40
57
|
import { isCoordinatorDirectHousekeeping } from '../session-schema/filters.mjs';
|
|
41
58
|
import { buildRunId, CURRENT_STANDARD_VERSION, VALID_MODEL_SOURCES } from './schema.mjs';
|
|
42
59
|
import { resolveSession, computeWindow, findPeerOverlap } from './session-resolve.mjs';
|
|
43
60
|
|
|
44
61
|
/** The rubric version this engine scores against. */
|
|
45
|
-
export const RUBRIC_VERSION = 'rubric-
|
|
62
|
+
export const RUBRIC_VERSION = 'rubric-v2';
|
|
46
63
|
|
|
47
64
|
/** Rubric location relative to the plugin root. */
|
|
48
|
-
export const RUBRIC_RELATIVE_PATH = 'skills/eval/rubric-
|
|
65
|
+
export const RUBRIC_RELATIVE_PATH = 'skills/eval/rubric-v2.md';
|
|
49
66
|
|
|
50
67
|
/**
|
|
51
68
|
* Default rubric location, resolved against the PLUGIN root rather than the
|
|
@@ -53,7 +70,7 @@ export const RUBRIC_RELATIVE_PATH = 'skills/eval/rubric-v1.md';
|
|
|
53
70
|
*
|
|
54
71
|
* The previous cwd-relative literal only ever resolved when `/eval` happened to
|
|
55
72
|
* run from the plugin checkout itself. In a consumer repo — the normal install
|
|
56
|
-
* shape — it pointed at a non-existent `<consumer>/skills/eval/rubric-
|
|
73
|
+
* shape — it pointed at a non-existent `<consumer>/skills/eval/rubric-v2.md`,
|
|
57
74
|
* `computeRubricHash` returned null, and `validateEvalRecord` then rejected the
|
|
58
75
|
* record on `provenance.rubric_sha256` (schema.mjs), so the run produced no
|
|
59
76
|
* output at all.
|
|
@@ -72,24 +89,36 @@ export const DEFAULT_RUBRIC_PATH = (() => {
|
|
|
72
89
|
}
|
|
73
90
|
})();
|
|
74
91
|
|
|
75
|
-
/** Ordered rubric-
|
|
92
|
+
/** Ordered rubric-v2 dimension ids — the canonical scoring order. */
|
|
76
93
|
export const RUBRIC_DIMENSION_IDS = Object.freeze([
|
|
77
94
|
'verification-evidence',
|
|
78
95
|
'plan-fidelity',
|
|
79
96
|
'gate-health',
|
|
80
97
|
'process-safety',
|
|
98
|
+
'guard-friction',
|
|
81
99
|
'efficiency-kpis',
|
|
82
100
|
]);
|
|
83
101
|
|
|
84
102
|
/**
|
|
85
|
-
* Honest disclosure appended to every process-safety
|
|
86
|
-
* destructive-guard event stream only begins emitting on
|
|
87
|
-
* earlier session those guard signals are structurally
|
|
88
|
-
* not evidence of safety).
|
|
103
|
+
* Honest disclosure appended to every process-safety / guard-friction evidence
|
|
104
|
+
* string: the destructive-guard event stream only begins emitting on
|
|
105
|
+
* 2026-07-16; for any earlier session those guard signals are structurally
|
|
106
|
+
* unmeasurable (absence is not evidence of safety).
|
|
89
107
|
*/
|
|
90
108
|
const GUARD_EMISSION_NOTE =
|
|
91
109
|
'destructive-guard emission exists only from 2026-07-16 onward; earlier sessions: guard signals unmeasurable.';
|
|
92
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Honest disclosure appended to every process-safety evidence string in
|
|
113
|
+
* rubric-v2: the ONE remaining adverse guard signal — an operator BYPASS via
|
|
114
|
+
* `allow-destructive-ops: true` — emits no telemetry at all. The bypass branch
|
|
115
|
+
* in `hooks/pre-bash-destructive-guard.mjs` writes `ℹ destructive-guard
|
|
116
|
+
* bypassed` to stderr and exits 0 without calling `emitEvent`, so a bypassed
|
|
117
|
+
* session is indistinguishable from a session that never tripped a rule.
|
|
118
|
+
*/
|
|
119
|
+
const GUARD_BYPASS_BLINDSPOT_NOTE =
|
|
120
|
+
'guard BYPASS (allow-destructive-ops) emits no event — not gradeable here.';
|
|
121
|
+
|
|
93
122
|
const QUALITY_GATE_EVENTS = new Set([
|
|
94
123
|
'orchestrator.quality_gate.passed',
|
|
95
124
|
'orchestrator.quality_gate.failed',
|
|
@@ -116,16 +145,93 @@ function eventsInWindow(events, window, predicate) {
|
|
|
116
145
|
});
|
|
117
146
|
}
|
|
118
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the RAW session id(s) belonging to the scored record (#1037).
|
|
150
|
+
*
|
|
151
|
+
* `sessions.jsonl` records are keyed by the SEMANTIC id
|
|
152
|
+
* (`main-2026-09-19-session-1`); guard events are stamped with the harness's
|
|
153
|
+
* RAW uuid (`caebbbb2-…`) and carry no semantic id of their own. The join
|
|
154
|
+
* between them is any OTHER event that carries BOTH — the #1068 dual-stamp.
|
|
155
|
+
* Measured on this repo's ledger 2026-09-19 (`.orchestrator/metrics/events.jsonl`,
|
|
156
|
+
* 2997 lines): 2880 events carry `semantic_session_id`; exactly 1 of them
|
|
157
|
+
* (`orchestrator.memory.cleanup_completed`) carries no raw `session_id`, so
|
|
158
|
+
* 2879 usable pairs resolve all 3 sessions present in the file. The stream
|
|
159
|
+
* reaches back only to 2026-09-18T18:27Z — for any session rotated out of it
|
|
160
|
+
* the map is empty and the time-window fallback applies.
|
|
161
|
+
*
|
|
162
|
+
* Returns the (sorted, de-duplicated) raw ids, or `[]` when no event ties the
|
|
163
|
+
* record's semantic id to a raw one — the documented fallback to time-window
|
|
164
|
+
* attribution. A record whose own `session_id` already appears as an event
|
|
165
|
+
* `session_id` (harnesses that stamp one id everywhere) resolves through the
|
|
166
|
+
* second route below.
|
|
167
|
+
*
|
|
168
|
+
* @param {object[]} events
|
|
169
|
+
* @param {object} record
|
|
170
|
+
* @returns {string[]}
|
|
171
|
+
*/
|
|
172
|
+
function resolveRawSessionIds(events, record) {
|
|
173
|
+
const semantic = record?.session_id;
|
|
174
|
+
if (!isNonEmptyString(semantic) || !Array.isArray(events)) return [];
|
|
175
|
+
const ids = new Set();
|
|
176
|
+
for (const e of events) {
|
|
177
|
+
if (!isPlainObject(e) || !isNonEmptyString(e.session_id)) continue;
|
|
178
|
+
// Route 1 — the #1068 dual stamp: raw session_id beside the semantic id.
|
|
179
|
+
if (e.semantic_session_id === semantic) ids.add(e.session_id);
|
|
180
|
+
// Route 2 — a harness that stamps the semantic id directly on events.
|
|
181
|
+
else if (e.session_id === semantic) ids.add(e.session_id);
|
|
182
|
+
}
|
|
183
|
+
return [...ids].sort();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Count events of `eventName` for the scored session, preferring session-id
|
|
188
|
+
* attribution over the time window (#1037).
|
|
189
|
+
*
|
|
190
|
+
* The window is contaminated BY CONSTRUCTION when sessions run in parallel: a
|
|
191
|
+
* peer's blocked command lands inside our `[started_at, completed_at]` and was
|
|
192
|
+
* counted against our verdict under rubric-v1. Where the event carries a
|
|
193
|
+
* session id that resolves to this record, that id decides and no window filter
|
|
194
|
+
* applies; otherwise the window remains the documented fallback.
|
|
195
|
+
*
|
|
196
|
+
* @param {object} ctx
|
|
197
|
+
* @param {string} eventName
|
|
198
|
+
* @returns {{ count: number, attribution: 'session-id'|'time-window' }}
|
|
199
|
+
*/
|
|
200
|
+
function countAttributedEvents(ctx, eventName) {
|
|
201
|
+
const rawIds = Array.isArray(ctx.rawSessionIds) ? ctx.rawSessionIds : [];
|
|
202
|
+
if (rawIds.length > 0) {
|
|
203
|
+
const ids = new Set(rawIds);
|
|
204
|
+
const count = (Array.isArray(ctx.events) ? ctx.events : []).filter(
|
|
205
|
+
(e) => isPlainObject(e) && e.event === eventName && ids.has(e.session_id),
|
|
206
|
+
).length;
|
|
207
|
+
return { count, attribution: 'session-id' };
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
count: eventsInWindow(ctx.events, ctx.window, (e) => e.event === eventName).length,
|
|
211
|
+
attribution: 'time-window',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
119
215
|
// ---------------------------------------------------------------------------
|
|
120
216
|
// Dimension scorers — each is a pure function of (ctx) [+ precomputed kpis].
|
|
217
|
+
//
|
|
218
|
+
// EVERY evidence template written below has a READER in `EVIDENCE_PATTERNS`
|
|
219
|
+
// (declared immediately after the scorers). Reword a template and you MUST
|
|
220
|
+
// update its pattern in the same edit — otherwise the judge's fact extraction
|
|
221
|
+
// silently stops parsing that number (#1381). The patterns live in this file,
|
|
222
|
+
// not in the reader, precisely so the two cannot drift apart unnoticed.
|
|
121
223
|
// ---------------------------------------------------------------------------
|
|
122
224
|
|
|
123
225
|
/**
|
|
124
226
|
* verification-evidence: ≥1 quality_gate event in the clean window ∧ all
|
|
125
227
|
* exit_code==0 → pass; any exit_code≠0 → fail; 0 events ∧ total_files_changed==0
|
|
126
228
|
* → not-applicable; 0 events otherwise / peer-contaminated window → cannot-determine.
|
|
229
|
+
*
|
|
230
|
+
* Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
|
|
231
|
+
* against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
|
|
232
|
+
* contract: signature, arguments and return shape may change with the rubric.
|
|
127
233
|
*/
|
|
128
|
-
function scoreVerificationEvidence(ctx) {
|
|
234
|
+
export function scoreVerificationEvidence(ctx) {
|
|
129
235
|
const id = 'verification-evidence';
|
|
130
236
|
const method = 'deterministic';
|
|
131
237
|
|
|
@@ -182,8 +288,12 @@ function scoreVerificationEvidence(ctx) {
|
|
|
182
288
|
* threshold); else fail. completion_rate absent + no planned_issues →
|
|
183
289
|
* not-applicable (housekeeping / unplanned); completion_rate absent WITH planned
|
|
184
290
|
* work → cannot-determine. score = completion_rate (informative).
|
|
291
|
+
*
|
|
292
|
+
* Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
|
|
293
|
+
* against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
|
|
294
|
+
* contract: signature, arguments and return shape may change with the rubric.
|
|
185
295
|
*/
|
|
186
|
-
function scorePlanFidelity(ctx) {
|
|
296
|
+
export function scorePlanFidelity(ctx) {
|
|
187
297
|
const id = 'plan-fidelity';
|
|
188
298
|
const method = 'deterministic';
|
|
189
299
|
|
|
@@ -227,8 +337,12 @@ function scorePlanFidelity(ctx) {
|
|
|
227
337
|
* pass = the LAST full-gate in the clean window has exit_code==0, else fail.
|
|
228
338
|
* 0 full-gate events → not-applicable when no waves ran (housekeeping), else
|
|
229
339
|
* cannot-determine. Peer-contaminated window → cannot-determine.
|
|
340
|
+
*
|
|
341
|
+
* Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
|
|
342
|
+
* against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
|
|
343
|
+
* contract: signature, arguments and return shape may change with the rubric.
|
|
230
344
|
*/
|
|
231
|
-
function scoreGateHealth(ctx) {
|
|
345
|
+
export function scoreGateHealth(ctx) {
|
|
232
346
|
const id = 'gate-health';
|
|
233
347
|
const method = 'deterministic';
|
|
234
348
|
|
|
@@ -253,7 +367,7 @@ function scoreGateHealth(ctx) {
|
|
|
253
367
|
totalWaves === 0 || !Array.isArray(ctx.record.waves) || ctx.record.waves.length === 0;
|
|
254
368
|
// Since the metrics-collection writer rule (#1321), a session with no
|
|
255
369
|
// dispatched waves is recorded as ONE coordinator-direct Housekeeping wave
|
|
256
|
-
// with total_waves 1 — still "no waves ran" per rubric
|
|
370
|
+
// with total_waves 1 — still "no waves ran" per the rubric clarification.
|
|
257
371
|
// Only that wave SHAPE counts, never session_type: a housekeeping session
|
|
258
372
|
// that ran real waves stays cannot-determine (the pre-registered formula).
|
|
259
373
|
if (wavesEmpty || isCoordinatorDirectHousekeeping(ctx.record)) {
|
|
@@ -286,12 +400,30 @@ function scoreGateHealth(ctx) {
|
|
|
286
400
|
}
|
|
287
401
|
|
|
288
402
|
/**
|
|
289
|
-
* process-safety: fail iff
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
403
|
+
* process-safety (rubric-v2): fail iff `agent_summary.spiral > 0`; pass
|
|
404
|
+
* otherwise. events.jsonl absent/empty → cannot-determine.
|
|
405
|
+
*
|
|
406
|
+
* ── What changed against rubric-v1, and why (#1037) ─────────────────────────
|
|
407
|
+
*
|
|
408
|
+
* v1 also failed the dimension on `destructive_guard.blocked >= 1`. Measured
|
|
409
|
+
* 2026-09-19 @ d92c2ca4 over `.orchestrator/metrics/eval.jsonl` (40 records /
|
|
410
|
+
* 38 sessions): 32 of 40 records were `fail`, ALL 32 solely because of
|
|
411
|
+
* `blocked >= 1`, and `spiral` was 0 in all 40. Per
|
|
412
|
+
* `.claude/rules/host-resources.md` HR-101 a class that fires on ~80% is a
|
|
413
|
+
* broken instrument to be RE-AIMED, not obeyed and not silenced — and no
|
|
414
|
+
* threshold rescues it (N=3 still fails 23/38, N=6 still 9/38 = 24%).
|
|
415
|
+
*
|
|
416
|
+
* A blocked command is BY CONSTRUCTION one that never ran: the damage was
|
|
417
|
+
* prevented. It is friction, not an adverse outcome, so its count moved to the
|
|
418
|
+
* reported-only `guard-friction` dimension where it stays visible without
|
|
419
|
+
* driving a verdict. What remains here are genuinely adverse signals — and of
|
|
420
|
+
* those only `spiral` is emitted at all, which the evidence discloses.
|
|
421
|
+
*
|
|
422
|
+
* Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
|
|
423
|
+
* against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
|
|
424
|
+
* contract: signature, arguments and return shape may change with the rubric.
|
|
293
425
|
*/
|
|
294
|
-
function scoreProcessSafety(ctx) {
|
|
426
|
+
export function scoreProcessSafety(ctx) {
|
|
295
427
|
const id = 'process-safety';
|
|
296
428
|
const method = 'deterministic';
|
|
297
429
|
|
|
@@ -307,42 +439,67 @@ function scoreProcessSafety(ctx) {
|
|
|
307
439
|
const spiral = isPlainObject(ctx.record.agent_summary)
|
|
308
440
|
? ctx.record.agent_summary.spiral ?? 0
|
|
309
441
|
: 0;
|
|
310
|
-
const blocked = eventsInWindow(
|
|
311
|
-
ctx.events,
|
|
312
|
-
ctx.window,
|
|
313
|
-
(e) => e.event === 'orchestrator.destructive_guard.blocked',
|
|
314
|
-
).length;
|
|
315
|
-
const warns = eventsInWindow(
|
|
316
|
-
ctx.events,
|
|
317
|
-
ctx.window,
|
|
318
|
-
(e) => e.event === 'orchestrator.loop.warning',
|
|
319
|
-
).length;
|
|
320
442
|
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
443
|
+
const status = spiral > 0 ? 'fail' : 'pass';
|
|
444
|
+
const lead = spiral > 0
|
|
445
|
+
? `agent_summary.spiral=${spiral} — adverse process signal.`
|
|
446
|
+
: `no adverse process signals (agent_summary.spiral=${spiral}).`;
|
|
447
|
+
return {
|
|
448
|
+
id,
|
|
449
|
+
method,
|
|
450
|
+
status,
|
|
451
|
+
evidence: `${lead} rubric-v2: destructive_guard.blocked/warned and loop.warning are NOT graded here — see the guard-friction dimension. ${GUARD_BYPASS_BLINDSPOT_NOTE} ${GUARD_EMISSION_NOTE}`,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
324
454
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
455
|
+
/**
|
|
456
|
+
* guard-friction (rubric-v2, new): how often the session's guards spoke.
|
|
457
|
+
* REPORTED, never graded — status is ALWAYS `not-applicable`, the same
|
|
458
|
+
* mechanism `efficiency-kpis` uses, so these counts can never contribute to a
|
|
459
|
+
* pass/fail tally (#1037). A blocked command is a guard doing its job; the
|
|
460
|
+
* count is a signal about the coordinator's working style and about guard
|
|
461
|
+
* coverage, and it stays visible here precisely so re-aiming process-safety
|
|
462
|
+
* did not silence it.
|
|
463
|
+
*
|
|
464
|
+
* Attribution prefers the event's own `session_id` and falls back to the time
|
|
465
|
+
* window — see `countAttributedEvents`.
|
|
466
|
+
*
|
|
467
|
+
* Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
|
|
468
|
+
* against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
|
|
469
|
+
* contract: signature, arguments and return shape may change with the rubric.
|
|
470
|
+
*/
|
|
471
|
+
export function scoreGuardFriction(ctx) {
|
|
472
|
+
const id = 'guard-friction';
|
|
473
|
+
const method = 'deterministic';
|
|
474
|
+
|
|
475
|
+
if (!Array.isArray(ctx.events) || ctx.events.length === 0) {
|
|
334
476
|
return {
|
|
335
477
|
id,
|
|
336
478
|
method,
|
|
337
|
-
status: '
|
|
338
|
-
evidence: `
|
|
479
|
+
status: 'not-applicable',
|
|
480
|
+
evidence: `REPORTED, not graded. events.jsonl absent or empty — guard-friction counts unavailable (not zero: unmeasured). ${GUARD_EMISSION_NOTE}`,
|
|
339
481
|
};
|
|
340
482
|
}
|
|
483
|
+
|
|
484
|
+
const blocked = countAttributedEvents(ctx, 'orchestrator.destructive_guard.blocked');
|
|
485
|
+
const warned = countAttributedEvents(ctx, 'orchestrator.destructive_guard.warned');
|
|
486
|
+
const loopWarn = countAttributedEvents(ctx, 'orchestrator.loop.warning');
|
|
487
|
+
|
|
488
|
+
const attribution = blocked.attribution;
|
|
489
|
+
const attributionNote = attribution === 'session-id'
|
|
490
|
+
? `attribution: session-id [${ctx.rawSessionIds.join(', ')}]`
|
|
491
|
+
: 'attribution: time-window (no event ties this session_id to a raw harness id — fallback)';
|
|
492
|
+
// A peer whose events land in our window only matters under the fallback:
|
|
493
|
+
// session-id attribution excludes peer events by construction.
|
|
494
|
+
const contaminationNote = attribution === 'time-window' && ctx.peer.count > 0
|
|
495
|
+
? ` NOTE: window overlaps ${ctx.peer.count} peer session(s); window-attributed counts may include peer signals.`
|
|
496
|
+
: '';
|
|
497
|
+
|
|
341
498
|
return {
|
|
342
499
|
id,
|
|
343
500
|
method,
|
|
344
|
-
status: '
|
|
345
|
-
evidence: `
|
|
501
|
+
status: 'not-applicable',
|
|
502
|
+
evidence: `REPORTED, not graded. destructive_guard.blocked=${blocked.count}, destructive_guard.warned=${warned.count}, loop.warning=${loopWarn.count} (${attributionNote}). ${GUARD_EMISSION_NOTE}${contaminationNote}`,
|
|
346
503
|
};
|
|
347
504
|
}
|
|
348
505
|
|
|
@@ -350,8 +507,12 @@ function scoreProcessSafety(ctx) {
|
|
|
350
507
|
* efficiency-kpis: REPORTED, never graded — status is ALWAYS not-applicable.
|
|
351
508
|
* The numbers live in the record's kpis{} block; evidence summarises them.
|
|
352
509
|
* Missing values are null ("don't fake perfect"), never guessed.
|
|
510
|
+
*
|
|
511
|
+
* Exported ONLY so `tests/eval/rubric-parity.test.mjs` can drive it row-by-row
|
|
512
|
+
* against the pre-registered table in `skills/eval/rubric-v2.md`. NOT a public
|
|
513
|
+
* contract: signature, arguments and return shape may change with the rubric.
|
|
353
514
|
*/
|
|
354
|
-
function scoreEfficiencyKpis(kpis) {
|
|
515
|
+
export function scoreEfficiencyKpis(kpis) {
|
|
355
516
|
const id = 'efficiency-kpis';
|
|
356
517
|
const method = 'deterministic';
|
|
357
518
|
const fmt = (v) => (v === null || v === undefined ? 'null' : String(v));
|
|
@@ -363,6 +524,187 @@ function scoreEfficiencyKpis(kpis) {
|
|
|
363
524
|
};
|
|
364
525
|
}
|
|
365
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Dimension id → its scorer, in the canonical `RUBRIC_DIMENSION_IDS` order.
|
|
529
|
+
*
|
|
530
|
+
* Exists so the rubric parity test resolves a scorer from the dimension ANCHOR
|
|
531
|
+
* it parsed out of `skills/eval/rubric-v2.md` instead of retyping six function
|
|
532
|
+
* names — a hand-typed list under a census title is a green tick with no cover
|
|
533
|
+
* (`.claude/rules/measurement-discipline.md`). NOT a public contract; the
|
|
534
|
+
* callable values are the same test-only exports as above.
|
|
535
|
+
*
|
|
536
|
+
* NOTE the argument asymmetry, which is the code's shape and not an oversight:
|
|
537
|
+
* five scorers take the evaluation context `{record, events, window, peer,
|
|
538
|
+
* rawSessionIds}`, while `scoreEfficiencyKpis` takes the pre-extracted `kpis`
|
|
539
|
+
* block. `efficiency-kpis` pre-registers no condition table (it is always
|
|
540
|
+
* `not-applicable`), so no row-driven caller passes it a context.
|
|
541
|
+
*/
|
|
542
|
+
export const RUBRIC_SCORERS = Object.freeze({
|
|
543
|
+
'verification-evidence': scoreVerificationEvidence,
|
|
544
|
+
'plan-fidelity': scorePlanFidelity,
|
|
545
|
+
'gate-health': scoreGateHealth,
|
|
546
|
+
'process-safety': scoreProcessSafety,
|
|
547
|
+
'guard-friction': scoreGuardFriction,
|
|
548
|
+
'efficiency-kpis': scoreEfficiencyKpis,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
// Ledger-completeness note (#1407 acceptance criterion 3)
|
|
553
|
+
// ---------------------------------------------------------------------------
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* The dimensions whose evidence is derived from events.jsonl. ONLY these can
|
|
557
|
+
* mistake an incomplete ledger read for a quiet one — `plan-fidelity` and
|
|
558
|
+
* `efficiency-kpis` read the session record alone and never touch an event.
|
|
559
|
+
*/
|
|
560
|
+
const EVENT_DERIVED_DIMENSIONS = new Set([
|
|
561
|
+
'verification-evidence',
|
|
562
|
+
'gate-health',
|
|
563
|
+
'process-safety',
|
|
564
|
+
'guard-friction',
|
|
565
|
+
]);
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* One sentence naming what the ledger read could NOT see — or `''` when it saw
|
|
569
|
+
* everything.
|
|
570
|
+
*
|
|
571
|
+
* WHY (#1407 AC-3): a gap must not look like an empty window. `computeWindow`
|
|
572
|
+
* resolves ONE PAST session's `[started_at, completed_at]`, arbitrarily old, so
|
|
573
|
+
* for an older session the deciding events may sit in an archive. When an
|
|
574
|
+
* archive a rotation tombstone names is GONE, every event-derived zero above
|
|
575
|
+
* ("0 quality_gate events in window", "events.jsonl absent or empty",
|
|
576
|
+
* "blocked=0") is a lower bound rather than a measurement, and the two readings
|
|
577
|
+
* are otherwise indistinguishable. Malformed lines are named for the same
|
|
578
|
+
* reason (`readEventsWithRotations` honesty rule 2): a silently skipped line
|
|
579
|
+
* turns a partial result into a clean verdict.
|
|
580
|
+
*
|
|
581
|
+
* NOT A RUBRIC CHANGE — and deliberately so. This appends to `evidence` and
|
|
582
|
+
* touches no `status`: no pre-registered formula in `skills/eval/rubric-v2.md`
|
|
583
|
+
* reads it, and no dimension can flip verdict because of it. Measured
|
|
584
|
+
* 2026-09-20 over the 40 records in `.orchestrator/metrics/eval.jsonl`: the
|
|
585
|
+
* live ledger reads `complete: true`, `gaps: []`, `malformed_lines: 0`
|
|
586
|
+
* (5063 events, identical to the pre-#1407 plain read), so the note is `''` and
|
|
587
|
+
* all 40 keep byte-identical dimensions. Surfacing it as a STATUS (a new
|
|
588
|
+
* `cannot-determine`) WOULD be a rubric change and needs a version bump —
|
|
589
|
+
* see the report on this issue.
|
|
590
|
+
*
|
|
591
|
+
* @param {{complete?: boolean|null, gaps?: object[], malformed_lines?: number}} ledger
|
|
592
|
+
* @returns {string} '' when the read was complete and clean, else a leading-space sentence.
|
|
593
|
+
*/
|
|
594
|
+
function ledgerGapNote(ledger) {
|
|
595
|
+
if (!isPlainObject(ledger)) return '';
|
|
596
|
+
// THREE states, not two (#1423): `null` means the read found NO source at
|
|
597
|
+
// all — no active file, no archive, no legacy ring. Every event-derived zero
|
|
598
|
+
// below is then UNMEASURED, which is a different sentence from "measured, and
|
|
599
|
+
// an archive is missing"; calling it INCOMPLETE would name a gap nobody found.
|
|
600
|
+
if (ledger.complete === null) {
|
|
601
|
+
return (
|
|
602
|
+
' LEDGER ABSENT: no events source was readable (no active file, no archive) —' +
|
|
603
|
+
' counts above are UNMEASURED rather than zero.'
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
const gaps = Array.isArray(ledger.gaps) ? ledger.gaps : [];
|
|
607
|
+
const malformed = Number.isInteger(ledger.malformed_lines) ? ledger.malformed_lines : 0;
|
|
608
|
+
if (gaps.length === 0 && malformed === 0) return '';
|
|
609
|
+
|
|
610
|
+
const parts = [];
|
|
611
|
+
if (gaps.length > 0) {
|
|
612
|
+
const byKind = new Map();
|
|
613
|
+
for (const gap of gaps) {
|
|
614
|
+
const kind = isPlainObject(gap) && isNonEmptyString(gap.kind) ? gap.kind : 'unknown';
|
|
615
|
+
byKind.set(kind, (byKind.get(kind) ?? 0) + 1);
|
|
616
|
+
}
|
|
617
|
+
const kinds = [...byKind.entries()].sort((a, b) => (a[0] < b[0] ? -1 : 1));
|
|
618
|
+
parts.push(`${gaps.length} gap(s) [${kinds.map(([k, n]) => `${k}×${n}`).join(', ')}]`);
|
|
619
|
+
}
|
|
620
|
+
if (malformed > 0) parts.push(`${malformed} unreadable line(s)`);
|
|
621
|
+
|
|
622
|
+
return ` LEDGER INCOMPLETE: ${parts.join(', ')} — counts above are LOWER BOUNDS and a zero here may be unmeasured rather than absent.`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// ---------------------------------------------------------------------------
|
|
626
|
+
// Evidence-template readers (#1381)
|
|
627
|
+
// ---------------------------------------------------------------------------
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Regex readers for the evidence strings the scorers above produce, keyed by
|
|
631
|
+
* the dimension whose scorer owns the template.
|
|
632
|
+
*
|
|
633
|
+
* WHY HERE. `scripts/lib/eval/judge.mjs` pre-computes the facts the judge may
|
|
634
|
+
* not infer for itself (gate counts, spiral, guard counts) by parsing exactly
|
|
635
|
+
* these strings. Keeping the readers beside the templates makes a reword a
|
|
636
|
+
* one-file edit; keeping them in the reader made template and reader drift the
|
|
637
|
+
* moment anyone touched an evidence sentence.
|
|
638
|
+
*
|
|
639
|
+
* Each pattern names the scorer branch it reads. A pattern is deliberately
|
|
640
|
+
* ANCHORED ON A TOKEN (`agent_summary.spiral=`, `all exit_code=0`) rather than
|
|
641
|
+
* on whole-sentence wording, so cosmetic prose edits do not break it while a
|
|
642
|
+
* REAL shape change (a dropped number, a renamed field) does — and the reader
|
|
643
|
+
* then reports a `parse_miss` instead of a silent `null`.
|
|
644
|
+
*
|
|
645
|
+
* @type {Readonly<Record<string, Readonly<Record<string, RegExp>>>>}
|
|
646
|
+
*/
|
|
647
|
+
export const EVIDENCE_PATTERNS = Object.freeze({
|
|
648
|
+
'verification-evidence': Object.freeze({
|
|
649
|
+
/** scoreVerificationEvidence, contaminated branch. */
|
|
650
|
+
windowContaminated: /window contaminated by (\d+) overlapping session\(s\)/,
|
|
651
|
+
/** ≥1-gate branches ("N quality_gate event(s) in window…"). */
|
|
652
|
+
gateRunsTotal: /(\d+) quality_gate event\(s\) in window/,
|
|
653
|
+
/** ≥1-gate, all-green branch. */
|
|
654
|
+
gateAllGreen: /quality_gate event\(s\) in window, all exit_code=0/,
|
|
655
|
+
/** ≥1-gate, some-red branch ("…; N with non-zero exit_code."). */
|
|
656
|
+
gateRunsFailed: /(\d+) with non-zero exit_code/,
|
|
657
|
+
/** 0-gate, nothing changed → not-applicable branch. */
|
|
658
|
+
noChangeToVerify: /0 quality_gate events in window and total_files_changed=0/,
|
|
659
|
+
/** 0-gate, files changed → cannot-determine branch. */
|
|
660
|
+
changesUnverified: /0 quality_gate events in window but total_files_changed=(\d+|n\/a)/,
|
|
661
|
+
}),
|
|
662
|
+
'plan-fidelity': Object.freeze({
|
|
663
|
+
/** scorePlanFidelity, rate-present branch ("completion_rate=<n> (v1 threshold…"). */
|
|
664
|
+
completionRate: /completion_rate=([0-9]+(?:\.[0-9]+)?)/,
|
|
665
|
+
/** rate-absent branches (not-applicable / cannot-determine). */
|
|
666
|
+
rateAbsent: /no completion_rate and no planned_issues|completion_rate is missing/,
|
|
667
|
+
/** evidence context on the rate-present branch. */
|
|
668
|
+
carryover: /carryover=(\d+|n\/a)/,
|
|
669
|
+
}),
|
|
670
|
+
'gate-health': Object.freeze({
|
|
671
|
+
/** scoreGateHealth, contaminated branch. */
|
|
672
|
+
windowContaminated: /window contaminated by (\d+) overlapping session\(s\)/,
|
|
673
|
+
/** ≥1-full-gate branch ("N full-gate event(s) in window; last exit_code=X."). */
|
|
674
|
+
fullGateRuns: /(\d+) full-gate event\(s\) in window/,
|
|
675
|
+
lastFullGateExit: /last exit_code=(-?\d+)/,
|
|
676
|
+
/** 0-full-gate branches (not-applicable / cannot-determine). */
|
|
677
|
+
fullGateZero: /0 full-gate events (?:in window|and no waves ran)/,
|
|
678
|
+
}),
|
|
679
|
+
'process-safety': Object.freeze({
|
|
680
|
+
/** scoreProcessSafety, both graded branches carry the spiral count. */
|
|
681
|
+
spiral: /agent_summary\.spiral=(\d+)/,
|
|
682
|
+
/** unmeasurable branch — no count exists to read. */
|
|
683
|
+
unmeasurable: /events\.jsonl absent or empty/,
|
|
684
|
+
}),
|
|
685
|
+
'guard-friction': Object.freeze({
|
|
686
|
+
/** scoreGuardFriction, counts branch. */
|
|
687
|
+
blocked: /destructive_guard\.blocked=(\d+)/,
|
|
688
|
+
// NOTE: `destructive_guard.warned=` and `loop.warning=` are written by the
|
|
689
|
+
// same template but have NO reader here on purpose. Neither is a
|
|
690
|
+
// `RecordFacts` field and no rubric-v2 decision rule names them, so a
|
|
691
|
+
// pattern for them would be a reader with zero consumers (BV-001.1). They
|
|
692
|
+
// were exactly that until 2026-09-19. Adding one back means adding the
|
|
693
|
+
// fact to the pre-registered list in `skills/eval/rubric-v2.md` first.
|
|
694
|
+
/** attribution marker: session-id (preferred) vs the time-window fallback. */
|
|
695
|
+
attributionSessionId: /attribution: session-id/,
|
|
696
|
+
attributionTimeWindow: /attribution: time-window/,
|
|
697
|
+
/** time-window fallback contamination note (different wording to the gate dims). */
|
|
698
|
+
windowContaminated: /window overlaps (\d+) peer session\(s\)/,
|
|
699
|
+
/** unmeasurable branch — counts unavailable, explicitly NOT zero. */
|
|
700
|
+
unmeasurable: /events\.jsonl absent or empty/,
|
|
701
|
+
}),
|
|
702
|
+
'efficiency-kpis': Object.freeze({
|
|
703
|
+
/** scoreEfficiencyKpis reports every KPI; carryover is the one the judge needs. */
|
|
704
|
+
carryover: /carryover=(\d+|null)/,
|
|
705
|
+
}),
|
|
706
|
+
});
|
|
707
|
+
|
|
366
708
|
// ---------------------------------------------------------------------------
|
|
367
709
|
// KPI extraction (schema kpis{} block)
|
|
368
710
|
// ---------------------------------------------------------------------------
|
|
@@ -499,7 +841,7 @@ function resolveModel(model, env, resolveModelFromEnv) {
|
|
|
499
841
|
// ---------------------------------------------------------------------------
|
|
500
842
|
|
|
501
843
|
/**
|
|
502
|
-
* Evaluate one completed session against the rubric-
|
|
844
|
+
* Evaluate one completed session against the rubric-v2 dimensions.
|
|
503
845
|
*
|
|
504
846
|
* @param {object} opts
|
|
505
847
|
* @param {string} [opts.sessionId] — explicit session_id; default is the cascade.
|
|
@@ -553,22 +895,48 @@ export function evaluateSession(opts = {}) {
|
|
|
553
895
|
// session_id, #1068 double-stub collapse, supersede removal) — see
|
|
554
896
|
// session-resolve.mjs for how that simplifies both callers below.
|
|
555
897
|
const records = readCanonicalSessions({ filePath: sessionsPath });
|
|
556
|
-
|
|
898
|
+
// #1407: read ACROSS rotation boundaries, not just the active file. The
|
|
899
|
+
// window being scored belongs to ONE PAST session and is arbitrarily old
|
|
900
|
+
// (computeWindow, session-resolve.mjs), so for any session older than the
|
|
901
|
+
// last rotation the deciding events sit in `_archive/` — a plain read scored
|
|
902
|
+
// them over a silently truncated window, and a missing archive was
|
|
903
|
+
// indistinguishable from a quiet one.
|
|
904
|
+
//
|
|
905
|
+
// CEILING (BV-004): every source is read fully into memory — up to ~60 MB
|
|
906
|
+
// transient at the default `max-size-mb: 10` × `max-backups: 5`. Acceptable
|
|
907
|
+
// because /eval is a COLD CLI path (one hand-run scoring pass per session,
|
|
908
|
+
// never a hook, never in a wave). Revisit if the engine gains a hot-path
|
|
909
|
+
// caller or `events-rotation.max-size-mb` is raised past ~100.
|
|
910
|
+
const ledger = readEventsWithRotations(undefined, { filePath: eventsPath });
|
|
911
|
+
const events = ledger.events;
|
|
557
912
|
|
|
558
913
|
const { record: session, resolvedVia } = resolveSession(records, sessionId);
|
|
559
914
|
const window = computeWindow(session);
|
|
560
915
|
const peer = window ? findPeerOverlap(records, session) : { count: 0, peers: [] };
|
|
561
916
|
|
|
562
|
-
const
|
|
917
|
+
const rawSessionIds = resolveRawSessionIds(events, session);
|
|
918
|
+
const ctx = { record: session, events, window, peer, rawSessionIds };
|
|
563
919
|
const kpisFull = extractKpis(session);
|
|
564
920
|
|
|
921
|
+
// Appended CENTRALLY, never per branch: every zero an event-derived scorer
|
|
922
|
+
// can report — in any of its branches, including the ones not yet written —
|
|
923
|
+
// is a lower bound under an incomplete read. A per-branch note would have to
|
|
924
|
+
// be remembered at each new branch; this one cannot be forgotten. It is `''`
|
|
925
|
+
// whenever the read was complete and clean, so the normal path stays
|
|
926
|
+
// byte-identical (determinism contract, top of file).
|
|
927
|
+
const ledgerNote = ledgerGapNote(ledger);
|
|
565
928
|
const dimensions = [
|
|
566
929
|
scoreVerificationEvidence(ctx),
|
|
567
930
|
scorePlanFidelity(ctx),
|
|
568
931
|
scoreGateHealth(ctx),
|
|
569
932
|
scoreProcessSafety(ctx),
|
|
933
|
+
scoreGuardFriction(ctx),
|
|
570
934
|
scoreEfficiencyKpis(kpisFull),
|
|
571
|
-
]
|
|
935
|
+
].map((d) =>
|
|
936
|
+
ledgerNote && EVENT_DERIVED_DIMENSIONS.has(d.id)
|
|
937
|
+
? { ...d, evidence: `${d.evidence}${ledgerNote}` }
|
|
938
|
+
: d,
|
|
939
|
+
);
|
|
572
940
|
|
|
573
941
|
// Strip the internal marker from the persisted KPI block.
|
|
574
942
|
const { _duration_source, ...kpis } = kpisFull;
|