session-orchestrator 5.2.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +5 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +5 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +5 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
- package/.agents/skills/close/SKILL.md +5 -1
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +5 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +5 -1
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +5 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +5 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +6 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +6 -2
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +5 -1
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +5 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +5 -1
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +5 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/navigator/SKILL.md +22 -0
- package/.agents/skills/navigator/agents/openai.yaml +5 -0
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +5 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +5 -1
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +5 -1
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +5 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +5 -1
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +5 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +5 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +5 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +5 -1
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +5 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
- package/.codex-plugin/skills/eval/SKILL.md +1 -1
- package/.codex-plugin/skills/evolve/SKILL.md +1 -1
- package/.codex-plugin/skills/navigator/SKILL.md +22 -0
- package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
- package/.cursor/commands/eval.md +1 -1
- package/.cursor/commands/navigator.md +15 -0
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/rules/060-evolve.mdc +27 -12
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/navigator/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +12 -3
- package/AGENTS.md +9 -30
- package/CHANGELOG.md +206 -0
- package/README.md +13 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +7 -3
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +22 -7
- package/docs/ci-setup.md +57 -4
- package/docs/codex-setup.md +14 -2
- package/docs/components.md +12 -10
- package/docs/events-schema.md +65 -15
- package/docs/persona-panel.md +5 -4
- package/docs/rule-authoring.md +84 -17
- package/docs/scope-collision-guard.md +26 -4
- package/docs/session-config-reference.md +154 -12
- package/docs/session-config-template.md +41 -3
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +87 -6
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +58 -23
- package/hooks/on-session-start.mjs +101 -11
- package/hooks/on-stop.mjs +170 -22
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +3 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +202 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +15 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +16 -11
- package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
- package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +792 -74
- package/hooks/subagent-telemetry.mjs +111 -38
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +3 -3
- package/pi/prompts/eval.md +1 -1
- package/pi/prompts/navigator.md +12 -0
- package/pi/prompts/session.md +1 -1
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +7 -2
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +6 -2
- package/rules/opt-in-stack/backend-data.md +10 -2
- package/rules/opt-in-stack/backend.md +10 -4
- package/rules/opt-in-stack/frontend.md +7 -2
- package/rules/opt-in-stack/security-web.md +10 -4
- package/rules/opt-in-stack/swift.md +6 -2
- package/scripts/apply-session-learnings.mjs +492 -0
- package/scripts/archive-closed-prds.mjs +2 -1
- package/scripts/autopilot.mjs +70 -53
- package/scripts/backfill-abandoned-sessions.mjs +118 -16
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/cursor-install.mjs +1 -4
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +371 -31
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +22 -3
- package/scripts/fleet-checkin.mjs +147 -0
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-cursor-adapter.mjs +61 -16
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +18 -2
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +19 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/command-blocker.mjs +275 -28
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +163 -0
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +85 -10
- package/scripts/lib/convergence-monitor.mjs +74 -11
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +545 -7
- package/scripts/lib/fleet-protocol.mjs +270 -0
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +40 -5
- package/scripts/lib/gates/gate-full.mjs +35 -5
- package/scripts/lib/gates/gate-helpers.mjs +336 -23
- package/scripts/lib/gates/gate-incremental.mjs +49 -23
- package/scripts/lib/gates/gate-per-file.mjs +42 -8
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +208 -8
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
- package/scripts/lib/learnings/io.mjs +154 -26
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/maintenance-due-banner.mjs +53 -88
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +104 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
- package/scripts/lib/orphan-reaper/scan.mjs +506 -0
- package/scripts/lib/orphan-reaper.mjs +316 -0
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/process-group.mjs +950 -0
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +152 -33
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +141 -74
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +3 -4
- package/scripts/lib/scope-echo.mjs +39 -2
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +33 -6
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +27 -2
- package/scripts/lib/session-schema/constants.mjs +56 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/session-token-rollup.mjs +245 -52
- package/scripts/lib/sessions-canonical.mjs +118 -4
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/pricing.mjs +118 -71
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +140 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/user-invocable-skills.mjs +23 -3
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
- package/scripts/lib/validate/check-entry-guard.mjs +13 -50
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +9 -1
- package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
- package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
- package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
- package/scripts/lib/validate/check-unwired-features.mjs +130 -27
- package/scripts/lib/validate/check-validator-registration.mjs +34 -10
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +210 -84
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
- package/scripts/lib/vault-status/board-writer.mjs +48 -11
- package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +306 -0
- package/scripts/release.mjs +40 -8
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
- package/scripts/run-quality-gate.mjs +357 -197
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +56 -5
- package/scripts/sweep-expired-rules.mjs +16 -4
- package/scripts/validate-plugin.mjs +19 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +81 -47
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +131 -0
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/state-ownership.md +5 -0
- package/skills/autopilot/SKILL.md +47 -18
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/_shared-template.md +1 -1
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/eval/SKILL.md +39 -24
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +3 -3
- package/skills/evolve/references/evolve-analyze-mode.md +10 -6
- package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/navigator/SKILL.md +193 -0
- package/skills/navigator/references/account-check.sh +87 -0
- package/skills/navigator/references/fleet-decisions.mjs +231 -0
- package/skills/navigator/references/slot-check.sh +114 -0
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/reconcile/SKILL.md +11 -0
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/learning-patterns.md +22 -12
- package/skills/session-end/metrics-collection.md +5 -6
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +32 -60
- package/skills/session-plan/SKILL.md +17 -8
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +6 -0
- package/skills/session-start/references/operations-contract.md +4 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/ux-grill/SKILL.md +1 -1
- package/skills/wave-executor/SKILL.md +9 -5
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +3 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
- package/.cursor/commands/convergence-monitoring.md +0 -13
- package/.cursor/commands/npm-publish.md +0 -13
- package/pi/prompts/convergence-monitoring.md +0 -11
- package/pi/prompts/npm-publish.md +0 -11
package/scripts/emit-session.mjs
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* a single session record to `.orchestrator/metrics/sessions.jsonl` (or any
|
|
7
7
|
* target path). Replaces the raw shell `>>` append with a validated path:
|
|
8
8
|
*
|
|
9
|
-
* node scripts/emit-session.mjs [--file PATH] [--entry JSON]
|
|
9
|
+
* node scripts/emit-session.mjs [--file PATH] [--entry JSON] [--session-uuid ID]
|
|
10
10
|
*
|
|
11
11
|
* Input modes:
|
|
12
12
|
* --entry '<json>' pass the entry JSON literally (for shell pipelines)
|
|
13
13
|
* (stdin) read the entry JSON from stdin (default when no --entry)
|
|
14
14
|
*
|
|
15
|
+
* --session-uuid ID this session's raw harness UUID (overrides the
|
|
16
|
+
* current-session.json lookup; the test seam)
|
|
17
|
+
*
|
|
15
18
|
* Defaults:
|
|
16
19
|
* --file .orchestrator/metrics/sessions.jsonl
|
|
17
20
|
*
|
|
@@ -33,6 +36,8 @@ import {
|
|
|
33
36
|
stampMemoryCleanup,
|
|
34
37
|
} from './lib/memory-cleanup-stamp.mjs';
|
|
35
38
|
import { parseStateMd, readSessionProfile, resolveStateMdPath } from './lib/state-md.mjs';
|
|
39
|
+
import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
|
|
40
|
+
import { rollupSessionTokens } from './lib/session-token-rollup.mjs';
|
|
36
41
|
import { serializeSessionLineChecked } from './lib/session-schema/serializer.mjs';
|
|
37
42
|
import {
|
|
38
43
|
validateSession,
|
|
@@ -40,22 +45,168 @@ import {
|
|
|
40
45
|
CURRENT_SESSION_SCHEMA_VERSION,
|
|
41
46
|
clampTimestampsMonotonic,
|
|
42
47
|
aliasLegacyEndedAt,
|
|
48
|
+
normalizeWaveKeys,
|
|
43
49
|
} from './lib/session-schema.mjs';
|
|
44
50
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
45
51
|
|
|
46
52
|
export { serializeSessionLineChecked };
|
|
47
53
|
|
|
54
|
+
// A FULL object name only (SHA-1 or SHA-256 repo). A short sha or a symbolic
|
|
55
|
+
// ref (`HEAD`, a branch) would resolve to a DIFFERENT commit when read later.
|
|
56
|
+
const FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
57
|
+
|
|
58
|
+
// Rollup fields merged into the record (#1436) — the totals plus the #1244
|
|
59
|
+
// cache buckets and contract marker the former prose recipe merged, and the
|
|
60
|
+
// #1475 cost-coverage counters, which say why `total_cost_usd` is absent when
|
|
61
|
+
// priced < total. `legacy_v1_records` stays in the rollup's own return value.
|
|
62
|
+
const ROLLUP_KEYS = Object.freeze([
|
|
63
|
+
'total_tokens',
|
|
64
|
+
'total_token_input',
|
|
65
|
+
'total_token_output',
|
|
66
|
+
'total_token_input_uncached',
|
|
67
|
+
'total_token_cache_read',
|
|
68
|
+
'total_token_cache_creation',
|
|
69
|
+
'total_cost_usd',
|
|
70
|
+
'subagents_with_tokens',
|
|
71
|
+
'matched_records',
|
|
72
|
+
'cost_records_priced',
|
|
73
|
+
'cost_records_total',
|
|
74
|
+
'_token_schema',
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
// Rollup keys validateSession() requires to be non-negative integers.
|
|
78
|
+
const INTEGER_ROLLUP_KEYS = new Set([
|
|
79
|
+
'total_tokens',
|
|
80
|
+
'matched_records',
|
|
81
|
+
'cost_records_priced',
|
|
82
|
+
'cost_records_total',
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve THIS session's raw harness UUID — the join key `subagents.jsonl`
|
|
87
|
+
* (`parent_session_id`) and `events.jsonl` (`session_id`) carry, which the
|
|
88
|
+
* record itself does not (its `session_id` is the semantic slug).
|
|
89
|
+
*
|
|
90
|
+
* `--session-uuid` wins (an explicit assertion, and the test seam). Otherwise
|
|
91
|
+
* `.orchestrator/current-session.json` is read. That file is SHARED by the
|
|
92
|
+
* working copy — the last SessionStart hook wrote it — so its UUID is adopted
|
|
93
|
+
* ONLY when its `semantic_session_id` (or, for a legacy raw-id record, its
|
|
94
|
+
* `session_id`) equals this record's `session_id`: the same "named owner
|
|
95
|
+
* equals the record" gate as the STATE.md derivation in main(). When this
|
|
96
|
+
* process carries its own session id (`readProcessLocalSessionIds()` — the
|
|
97
|
+
* harness env, which a dispatched subagent inherits from the coordinator), that
|
|
98
|
+
* witness OUTRANKS the shared marker: a marker UUID the process does not carry
|
|
99
|
+
* was written by a peer and is refused even when the labels agree (two sessions
|
|
100
|
+
* can mint the same semantic label, #1066). Ranked, never unioned
|
|
101
|
+
* (.claude/rules/identity-and-locks.md). A foreign marker is WARNed and yields
|
|
102
|
+
* null; an absent marker yields null silently.
|
|
103
|
+
*
|
|
104
|
+
* CEILING: a resumed session that received a NEW raw id keeps its semantic id,
|
|
105
|
+
* so only the latest raw id resolves and subagents dispatched under the earlier
|
|
106
|
+
* one are not rolled up. Revisit if current-session.json gains a raw-id history.
|
|
107
|
+
*
|
|
108
|
+
* Never throws.
|
|
109
|
+
*
|
|
110
|
+
* @param {{ override: string|null, recordSessionId: unknown }} opts
|
|
111
|
+
* @returns {string|null}
|
|
112
|
+
*/
|
|
113
|
+
function resolveOwnSessionUuid({ override, recordSessionId }) {
|
|
114
|
+
const trimmed = (v) => (typeof v === 'string' ? v.trim() : '');
|
|
115
|
+
const explicit = trimmed(override);
|
|
116
|
+
if (explicit) return explicit;
|
|
117
|
+
const markerPath = join(process.cwd(), '.orchestrator', 'current-session.json');
|
|
118
|
+
let marker;
|
|
119
|
+
try {
|
|
120
|
+
if (!existsSync(markerPath)) return null;
|
|
121
|
+
marker = JSON.parse(readFileSync(markerPath, 'utf8'));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
process.stderr.write(
|
|
124
|
+
`emit-session: WARN could not read ${markerPath} (${err?.message ?? err}); ` +
|
|
125
|
+
`omitting raw_session_id and the token rollup\n`
|
|
126
|
+
);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const uuid = trimmed(marker?.session_id);
|
|
130
|
+
if (!uuid) return null;
|
|
131
|
+
const semantic = trimmed(marker?.semantic_session_id);
|
|
132
|
+
const namesRecord = (semantic !== '' && semantic === recordSessionId) || uuid === recordSessionId;
|
|
133
|
+
const processIds = readProcessLocalSessionIds();
|
|
134
|
+
const processAgrees = processIds.length === 0 || processIds.includes(uuid);
|
|
135
|
+
if (namesRecord && processAgrees) return uuid;
|
|
136
|
+
// The record's session_id is the semantic label (STATE.md `session`); name
|
|
137
|
+
// both sides of whichever comparison failed, so the operator sees which drifted.
|
|
138
|
+
const why = namesRecord
|
|
139
|
+
? `session label ${recordSessionId} matches, but this process runs as ` +
|
|
140
|
+
`${processIds.join('/')}, not the marker's uuid ${uuid}`
|
|
141
|
+
: `session label mismatch: marker semantic_session_id=${semantic || '<absent>'} ` +
|
|
142
|
+
`vs record session_id=${recordSessionId ?? '<unknown>'}`;
|
|
143
|
+
process.stderr.write(
|
|
144
|
+
`emit-session: WARN current-session.json ${why}; omitting raw_session_id and the token rollup\n`
|
|
145
|
+
);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The `head_sha` of THIS session's own `orchestrator.session.started` event —
|
|
151
|
+
* matched on the raw UUID, so a parallel session's start event never supplies
|
|
152
|
+
* it. The FIRST own event decides: a resume or compact that re-emits the event
|
|
153
|
+
* under the same raw id is not the session's start, so its sha is never taken —
|
|
154
|
+
* not even when the first own event carries none. Returns null when there is no
|
|
155
|
+
* own event, or the first one's head_sha is absent or not a full sha.
|
|
156
|
+
*
|
|
157
|
+
* LIMIT — rotation (#1457 point 3): events.jsonl rotates at SessionStart once
|
|
158
|
+
* it exceeds `events-rotation.max-size-mb` (default 10), and when that start is
|
|
159
|
+
* a compact/resume of THIS session the first SURVIVING own event can be a
|
|
160
|
+
* compact/resume re-emit whose head_sha is later than the real start. When the
|
|
161
|
+
* first own event carries `native_source` 'compact' or 'resume' it is provably
|
|
162
|
+
* not the start, so this returns null (unknown) rather than a wrong ref. An
|
|
163
|
+
* event without `native_source` (Codex/Cursor, or a pre-#1091 writer) is still
|
|
164
|
+
* taken — so a rotation that leaves an unlabelled re-emit first stays wrong.
|
|
165
|
+
*
|
|
166
|
+
* Whole-file read, like deriveMemoryCleanupSignal() on the same file — fine at
|
|
167
|
+
* today's events.jsonl size; revisit if the file outgrows a single read.
|
|
168
|
+
*
|
|
169
|
+
* Never throws.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} eventsFile
|
|
172
|
+
* @param {string} uuid
|
|
173
|
+
* @returns {string|null}
|
|
174
|
+
*/
|
|
175
|
+
function readOwnStartHeadSha(eventsFile, uuid) {
|
|
176
|
+
let raw;
|
|
177
|
+
try {
|
|
178
|
+
raw = readFileSync(eventsFile, 'utf8');
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
for (const line of raw.split('\n')) {
|
|
183
|
+
if (!line.includes(uuid)) continue;
|
|
184
|
+
let ev;
|
|
185
|
+
try {
|
|
186
|
+
ev = JSON.parse(line);
|
|
187
|
+
} catch {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (ev?.event !== 'orchestrator.session.started' || ev.session_id !== uuid) continue;
|
|
191
|
+
if (ev.native_source === 'compact' || ev.native_source === 'resume') return null;
|
|
192
|
+
return typeof ev.head_sha === 'string' && FULL_SHA_RE.test(ev.head_sha) ? ev.head_sha : null;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
48
197
|
function parseArgs(argv) {
|
|
49
|
-
const args = { file: '.orchestrator/metrics/sessions.jsonl', entry: null };
|
|
198
|
+
const args = { file: '.orchestrator/metrics/sessions.jsonl', entry: null, sessionUuid: null };
|
|
50
199
|
for (let i = 0; i < argv.length; i++) {
|
|
51
200
|
const a = argv[i];
|
|
52
201
|
if (a === '--file') args.file = argv[++i];
|
|
53
202
|
else if (a === '--entry') args.entry = argv[++i];
|
|
203
|
+
else if (a === '--session-uuid') args.sessionUuid = argv[++i];
|
|
54
204
|
else if (a === '--help' || a === '-h') {
|
|
55
205
|
process.stdout.write(
|
|
56
206
|
'Usage: node scripts/emit-session.mjs [--file PATH] [--entry JSON]\n' +
|
|
57
207
|
' --file target JSONL file (default: .orchestrator/metrics/sessions.jsonl)\n' +
|
|
58
208
|
' --entry entry JSON (if omitted, read from stdin)\n' +
|
|
209
|
+
' --session-uuid raw harness UUID (default: owned .orchestrator/current-session.json)\n' +
|
|
59
210
|
'Exit codes: 0 append ok, 1 validation error, 2 I/O error\n'
|
|
60
211
|
);
|
|
61
212
|
process.exit(0);
|
|
@@ -118,6 +269,37 @@ async function main() {
|
|
|
118
269
|
);
|
|
119
270
|
}
|
|
120
271
|
|
|
272
|
+
// 3. Alias legacy per-wave agent-count keys (`agents_completed`, …) onto
|
|
273
|
+
// their canonical `agent_count_*` spelling (#1390 P1), non-clobbering.
|
|
274
|
+
// ONLY this pass of normalizeSession runs on the write path: the rest
|
|
275
|
+
// would tag a missing `schema_version` as 0 (validateSession stamps the
|
|
276
|
+
// current version) and apply top-level aliases that validateSession's
|
|
277
|
+
// contract deliberately does not.
|
|
278
|
+
repaired = normalizeWaveKeys(repaired);
|
|
279
|
+
|
|
280
|
+
// Own raw UUID (#1436), resolved once for the three derivations further down:
|
|
281
|
+
// raw_session_id, the session_start_ref events fallback, the token rollup.
|
|
282
|
+
const ownUuid = resolveOwnSessionUuid({
|
|
283
|
+
override: args.sessionUuid,
|
|
284
|
+
recordSessionId: repaired.session_id,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// An explicit session_start_ref that is not a full sha (#1443) names a
|
|
288
|
+
// DIFFERENT commit once the short form becomes ambiguous. Dropped with a WARN
|
|
289
|
+
// rather than rejected: the record is the load-bearing artefact, and the
|
|
290
|
+
// derivations below may still supply the full sha. Explicit `null` stays.
|
|
291
|
+
// Enforced HERE, not in validateSession(): the ledger already holds records
|
|
292
|
+
// with 7-char refs, and the session-start integrity banner runs every one of
|
|
293
|
+
// them through validateSession().
|
|
294
|
+
if (typeof repaired.session_start_ref === 'string' && !FULL_SHA_RE.test(repaired.session_start_ref)) {
|
|
295
|
+
process.stderr.write(
|
|
296
|
+
`emit-session: WARN session_start_ref=${repaired.session_start_ref} is not a full hex sha; ` +
|
|
297
|
+
`dropping it\n`
|
|
298
|
+
);
|
|
299
|
+
const { session_start_ref: _dropped, ...rest } = repaired;
|
|
300
|
+
repaired = rest;
|
|
301
|
+
}
|
|
302
|
+
|
|
121
303
|
// `memory_cleanup_at` derivation (#699 follow-up — Disziplin statt Mechanik).
|
|
122
304
|
// The flag used to be a boolean the coordinator-LLM remembered to pass at
|
|
123
305
|
// session-end; it was forgotten on 2026-08-14 and the cadence marker stalled
|
|
@@ -154,73 +336,231 @@ async function main() {
|
|
|
154
336
|
}
|
|
155
337
|
}
|
|
156
338
|
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
339
|
+
// STATE.md-derived enrichment — ONE read, ONE parse, ONE ownership check for
|
|
340
|
+
// both fields below.
|
|
341
|
+
//
|
|
342
|
+
// `session_profile` (#1247 — GitLab issue "key the effective-sizing row on
|
|
343
|
+
// waves x profile"). The mandatory write path is the coordinator composing
|
|
344
|
+
// `$METRICS_ENTRY` in prose (session-metrics-write.md), which never reliably
|
|
345
|
+
// set this field — so it is read here instead, from this repo's own STATE.md
|
|
346
|
+
// frontmatter via `readSessionProfile()` (scripts/lib/state-md.mjs).
|
|
347
|
+
//
|
|
348
|
+
// `session_start_ref` (#1339 P8) — frontmatter `session-start-ref`, the HEAD
|
|
349
|
+
// sha the wave-executor pinned at session start. `/evolve analyze` needs it
|
|
350
|
+
// for an attributable `<start>..<end>` commit range; without it, it falls
|
|
351
|
+
// back to a time window that also attributes a PARALLEL session's commits to
|
|
352
|
+
// this one (skills/evolve/references/evolve-analyze-mode.md). Only a full
|
|
353
|
+
// hex sha (FULL_SHA_RE) is adopted; anything else is omitted with a WARN.
|
|
354
|
+
// Ceiling: the frontmatter parser coerces an all-decimal-digit value to a
|
|
355
|
+
// number, so such a sha (p ~ 7e-9) is omitted, never adopted as a number.
|
|
356
|
+
// NO END REF, deliberately: this runs at session-end Phase 3.7, BEFORE the
|
|
357
|
+
// Phase 4 close commit exists, so HEAD here would cut the session's own
|
|
358
|
+
// commit out of its range. Consumers use HEAD + `--until=<completed_at>`.
|
|
359
|
+
// Revisit when a post-commit writer exists that can stamp `session_end_ref`.
|
|
162
360
|
//
|
|
163
|
-
// Precedence: an EXPLICIT
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// never coerced to `''`/`'none'`/a literal `"null"` string.
|
|
361
|
+
// Precedence: an EXPLICIT key on the incoming record (even `null`) wins and
|
|
362
|
+
// is never overwritten — same "explicit assertion beats derivation"
|
|
363
|
+
// convention as `memory_cleanup_at` above. Absence in STATE.md leaves the
|
|
364
|
+
// field OMITTED — never coerced to `''`/`'none'`/a literal `"null"` string.
|
|
168
365
|
//
|
|
169
366
|
// OWNERSHIP: STATE.md belongs to the session named in its OWN frontmatter
|
|
170
367
|
// (`session:`), which in a shared working copy need not be the session this
|
|
171
368
|
// record describes — two parallel sessions, or a `/close` run after a
|
|
172
369
|
// foreign `/plan` session left its STATE.md behind (see
|
|
173
370
|
// `.claude/rules/parallel-sessions.md`). Filing session B's waves under
|
|
174
|
-
// session A's profile is exactly the cross-contamination
|
|
175
|
-
//
|
|
176
|
-
// equals this record's `session_id`; on any mismatch (or an
|
|
177
|
-
// ownership, i.e. no `session` key)
|
|
371
|
+
// session A's profile — or A's start ref — is exactly the cross-contamination
|
|
372
|
+
// both fields exist to remove. So they are adopted ONLY when the frontmatter
|
|
373
|
+
// `session` equals this record's `session_id`; on any mismatch (or an
|
|
374
|
+
// unprovable ownership, i.e. no `session` key) they are OMITTED — never guessed.
|
|
178
375
|
//
|
|
179
376
|
// FAIL-SAFE: the read is enrichment; the session record is the load-bearing
|
|
180
377
|
// artefact. Any failure reading or parsing STATE.md (EISDIR when the path is
|
|
181
|
-
// a directory, EACCES/EPERM, a truncated file) omits the
|
|
378
|
+
// a directory, EACCES/EPERM, a truncated file) omits the fields and lets the
|
|
182
379
|
// record through — it must never be the reason `sessions.jsonl` gains no
|
|
183
380
|
// line at all.
|
|
184
|
-
|
|
381
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(repaired, key);
|
|
382
|
+
const needsProfile = !hasOwn('session_profile');
|
|
383
|
+
const needsStartRef = !hasOwn('session_start_ref');
|
|
384
|
+
// What STATE.md contributed to session_start_ref, for the WARN below when no
|
|
385
|
+
// source supplies one (#1457 point 2): none, or a ref that was discarded.
|
|
386
|
+
let stateMdStartRefNote = 'STATE.md carried none';
|
|
387
|
+
if (needsProfile || needsStartRef) {
|
|
185
388
|
let stateMdContents = '';
|
|
186
389
|
try {
|
|
187
390
|
const stateMdPath = resolveStateMdPath(process.cwd());
|
|
188
391
|
if (existsSync(stateMdPath)) stateMdContents = readFileSync(stateMdPath, 'utf8');
|
|
189
392
|
} catch (err) {
|
|
190
393
|
process.stderr.write(
|
|
191
|
-
`emit-session: WARN could not read STATE.md for session_profile
|
|
192
|
-
`(${err?.message ?? err}); omitting
|
|
394
|
+
`emit-session: WARN could not read STATE.md for session_profile/session_start_ref ` +
|
|
395
|
+
`derivation (${err?.message ?? err}); omitting both\n`
|
|
193
396
|
);
|
|
194
397
|
stateMdContents = '';
|
|
398
|
+
stateMdStartRefNote = 'STATE.md unreadable';
|
|
195
399
|
}
|
|
196
400
|
// No initialiser: both the try and the catch below assign `profile`.
|
|
197
401
|
let profile;
|
|
402
|
+
let startRef = null;
|
|
198
403
|
let owner = null;
|
|
199
404
|
try {
|
|
200
405
|
profile = readSessionProfile(stateMdContents);
|
|
201
|
-
const
|
|
202
|
-
const rawOwner =
|
|
406
|
+
const fm = parseStateMd(stateMdContents)?.frontmatter;
|
|
407
|
+
const rawOwner = fm?.session;
|
|
203
408
|
owner = typeof rawOwner === 'string' && rawOwner.trim().length > 0 ? rawOwner.trim() : null;
|
|
409
|
+
const rawRef = fm?.['session-start-ref'];
|
|
410
|
+
if (typeof rawRef === 'string' && FULL_SHA_RE.test(rawRef.trim())) {
|
|
411
|
+
startRef = rawRef.trim();
|
|
412
|
+
} else if (needsStartRef && rawRef !== undefined && rawRef !== null && rawRef !== '') {
|
|
413
|
+
process.stderr.write(
|
|
414
|
+
`emit-session: WARN STATE.md session-start-ref=${rawRef} is not a full hex sha; ` +
|
|
415
|
+
`omitting session_start_ref\n`
|
|
416
|
+
);
|
|
417
|
+
stateMdStartRefNote = 'STATE.md ref discarded (not a full hex sha)';
|
|
418
|
+
}
|
|
204
419
|
} catch (err) {
|
|
205
420
|
process.stderr.write(
|
|
206
|
-
`emit-session: WARN could not parse STATE.md for session_profile
|
|
207
|
-
`(${err?.message ?? err}); omitting
|
|
421
|
+
`emit-session: WARN could not parse STATE.md for session_profile/session_start_ref ` +
|
|
422
|
+
`derivation (${err?.message ?? err}); omitting both\n`
|
|
208
423
|
);
|
|
209
424
|
profile = null;
|
|
425
|
+
startRef = null;
|
|
426
|
+
stateMdStartRefNote = 'STATE.md unparseable';
|
|
210
427
|
}
|
|
211
|
-
|
|
428
|
+
const derived = {};
|
|
429
|
+
if (needsProfile && profile !== null) derived.session_profile = profile;
|
|
430
|
+
if (needsStartRef && startRef !== null) derived.session_start_ref = startRef;
|
|
431
|
+
const derivedKeys = Object.keys(derived);
|
|
432
|
+
if (derivedKeys.length > 0) {
|
|
212
433
|
if (owner !== null && owner === repaired.session_id) {
|
|
213
|
-
repaired = { ...repaired,
|
|
434
|
+
repaired = { ...repaired, ...derived };
|
|
214
435
|
} else {
|
|
436
|
+
if (derived.session_start_ref !== undefined) {
|
|
437
|
+
stateMdStartRefNote = `STATE.md ref discarded (belongs to session=${owner ?? '<absent>'})`;
|
|
438
|
+
}
|
|
215
439
|
// A visible omission: silence here is indistinguishable from "STATE.md
|
|
216
|
-
// carries no
|
|
217
|
-
// precisely what the operator wants to know about.
|
|
440
|
+
// carries no such field", and a foreign STATE.md in this working copy
|
|
441
|
+
// is precisely what the operator wants to know about.
|
|
442
|
+
process.stderr.write(
|
|
443
|
+
`emit-session: WARN STATE.md ${derivedKeys.map((k) => `${k}=${derived[k]}`).join(' ')} ` +
|
|
444
|
+
`belongs to session=${owner ?? '<absent>'}, not session_id=${repaired.session_id ?? '<unknown>'}; ` +
|
|
445
|
+
`omitting ${derivedKeys.join(', ')}\n`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// session_start_ref fallback (#1443): STATE.md supplied none (absent key,
|
|
452
|
+
// foreign owner, unparseable) — take the head_sha the SessionStart hook
|
|
453
|
+
// recorded on this session's own start event. Absent there too → omitted,
|
|
454
|
+
// with a WARN whether or not an own UUID was available (#1457 point 2) —
|
|
455
|
+
// without one the fallback cannot run, which is exactly when silence hid it.
|
|
456
|
+
if (!hasOwn('session_start_ref')) {
|
|
457
|
+
const headSha = ownUuid !== null
|
|
458
|
+
? readOwnStartHeadSha(join(dirname(args.file), 'events.jsonl'), ownUuid)
|
|
459
|
+
: null;
|
|
460
|
+
if (headSha !== null) {
|
|
461
|
+
repaired = { ...repaired, session_start_ref: headSha };
|
|
462
|
+
process.stderr.write(
|
|
463
|
+
`emit-session: derived session_start_ref=${headSha} from the own orchestrator.session.started event\n`
|
|
464
|
+
);
|
|
465
|
+
} else {
|
|
466
|
+
process.stderr.write(
|
|
467
|
+
`emit-session: WARN no session_start_ref — ${stateMdStartRefNote}, ` +
|
|
468
|
+
'no own session.started head_sha available\n'
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (ownUuid !== null && !hasOwn('raw_session_id')) {
|
|
474
|
+
repaired = { ...repaired, raw_session_id: ownUuid };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Token rollup (#1436) — mechanical, replacing the session-metrics-write.md
|
|
478
|
+
// prose step that had no production caller. Joins the sibling subagents.jsonl
|
|
479
|
+
// on the own raw UUID and fills ONLY keys the entry does not carry; an
|
|
480
|
+
// explicit `total_tokens` means the caller already rolled up, so nothing is
|
|
481
|
+
// merged. No matching records or a failed read OMITS the fields with a WARN —
|
|
482
|
+
// never a fabricated 0; a null total is omitted rather than written — for
|
|
483
|
+
// `total_cost_usd` that is any session with an unpriced subagent record
|
|
484
|
+
// (#1475: unknown model, no model, or no tokens), which the persisted
|
|
485
|
+
// `cost_records_priced < cost_records_total` then explains.
|
|
486
|
+
// match_status explains the omission; diagnostic fields stay out of the record.
|
|
487
|
+
if (ownUuid !== null && !hasOwn('total_tokens')) {
|
|
488
|
+
let rollup = null;
|
|
489
|
+
try {
|
|
490
|
+
rollup = rollupSessionTokens({
|
|
491
|
+
parentSessionId: ownUuid,
|
|
492
|
+
subagentsPath: join(dirname(args.file), 'subagents.jsonl'),
|
|
493
|
+
});
|
|
494
|
+
} catch (err) {
|
|
495
|
+
process.stderr.write(
|
|
496
|
+
`emit-session: WARN token rollup failed (${err?.message ?? err}); omitting token fields\n`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (rollup !== null && rollup.matched_records > 0) {
|
|
500
|
+
const merged = {};
|
|
501
|
+
for (const key of ROLLUP_KEYS) {
|
|
502
|
+
if (hasOwn(key) || !Number.isFinite(rollup[key])) continue;
|
|
503
|
+
// Mirrors validateSession(): these two are non-negative INTEGERS, while
|
|
504
|
+
// total_token_input/output may be fractional — one fractional token
|
|
505
|
+
// value upstream must cost the field, not the record.
|
|
506
|
+
if (INTEGER_ROLLUP_KEYS.has(key) && !Number.isInteger(rollup[key])) {
|
|
507
|
+
process.stderr.write(
|
|
508
|
+
`emit-session: WARN token rollup produced an invalid field (${key} must be a ` +
|
|
509
|
+
`non-negative integer, got: ${rollup[key]}); omitting ${key}\n`
|
|
510
|
+
);
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
merged[key] = rollup[key];
|
|
514
|
+
}
|
|
515
|
+
// Enrichment must never be the reason the ledger gains no line: validate
|
|
516
|
+
// the enriched record first and fall back to the un-enriched one. Blamed
|
|
517
|
+
// on the rollup only when the un-enriched record validates on its own —
|
|
518
|
+
// otherwise the validation below reports the record's own defect.
|
|
519
|
+
try {
|
|
520
|
+
validateSession({ ...repaired, ...merged });
|
|
521
|
+
repaired = { ...repaired, ...merged };
|
|
522
|
+
} catch (err) {
|
|
523
|
+
if (!(err instanceof ValidationError)) throw err;
|
|
524
|
+
let baseValid = true;
|
|
525
|
+
try {
|
|
526
|
+
validateSession(repaired);
|
|
527
|
+
} catch {
|
|
528
|
+
baseValid = false;
|
|
529
|
+
}
|
|
530
|
+
if (baseValid) {
|
|
531
|
+
process.stderr.write(
|
|
532
|
+
`emit-session: WARN token rollup produced an invalid field (${err.message}); ` +
|
|
533
|
+
`omitting token fields\n`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (rollup.total_tokens === null) {
|
|
218
538
|
process.stderr.write(
|
|
219
|
-
`emit-session: WARN
|
|
220
|
-
`
|
|
221
|
-
`omitting session_profile\n`
|
|
539
|
+
`emit-session: WARN token rollup matched ${rollup.matched_records} subagents.jsonl record(s) ` +
|
|
540
|
+
`for ${ownUuid}, none token-bearing; total_tokens omitted\n`
|
|
222
541
|
);
|
|
223
542
|
}
|
|
543
|
+
} else if (rollup !== null) {
|
|
544
|
+
let warning = `token rollup found no subagents.jsonl records for ${ownUuid}`;
|
|
545
|
+
switch (rollup.match_status) {
|
|
546
|
+
case 'ledger-absent':
|
|
547
|
+
warning += ' (ledger absent)';
|
|
548
|
+
break;
|
|
549
|
+
case 'ledger-empty':
|
|
550
|
+
warning += ' (ledger holds no readable records)';
|
|
551
|
+
break;
|
|
552
|
+
case 'unmatched':
|
|
553
|
+
warning = `token rollup found ${rollup.ledger_records} subagents.jsonl record(s) present, ` +
|
|
554
|
+
`none with parent_session_id=${ownUuid} — not attributable via this UUID, not zero cost`;
|
|
555
|
+
break;
|
|
556
|
+
case 'invalid-key':
|
|
557
|
+
warning = `token rollup rejected invalid parent_session_id=${ownUuid}`;
|
|
558
|
+
break;
|
|
559
|
+
default:
|
|
560
|
+
// Unknown or missing diagnostics retain the generic omission warning.
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
process.stderr.write(`emit-session: WARN ${warning}; omitting token fields\n`);
|
|
224
564
|
}
|
|
225
565
|
}
|
|
226
566
|
|
package/scripts/eval-session.mjs
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* Follows .claude/rules/cli-design.md:
|
|
9
9
|
* - `--json` for machine output; human-readable by default.
|
|
10
10
|
* - Data → stdout, diagnostics → stderr.
|
|
11
|
-
* - Exit codes: 0 success/match · 1 user-error/drift · 2 system error
|
|
11
|
+
* - Exit codes: 0 success/match · 1 user-error/drift · 2 system error ·
|
|
12
|
+
* 3 verify version-mismatch (non-standard, documented in --help).
|
|
12
13
|
*
|
|
13
14
|
* ## Append failures are NOT exit 0 (GitLab #969)
|
|
14
15
|
*
|
|
@@ -40,6 +41,17 @@
|
|
|
40
41
|
* Session-end Phase 3.7d remains advisory and MUST NOT gate on this exit code —
|
|
41
42
|
* it catches a non-zero exit and logs a WARN (skills/session-end/SKILL.md).
|
|
42
43
|
*
|
|
44
|
+
* ## A rubric change is not scoring drift (GitLab #1400)
|
|
45
|
+
*
|
|
46
|
+
* `--verify` re-scores a stored record with the CURRENT engine. When the stored
|
|
47
|
+
* `rubric_version` differs from `RUBRIC_VERSION`, the two sides answer different
|
|
48
|
+
* pre-registered check sets, so neither MATCH nor DRIFT is true: the run exits
|
|
49
|
+
* `3` with `verdict: "version-mismatch"` and re-scores nothing. Exit `0` would
|
|
50
|
+
* sell a cross-rubric comparison as a reproducibility proof; exit `1` would put
|
|
51
|
+
* a planned rubric bump in the same bucket as a forged record. Measured
|
|
52
|
+
* 2026-09-19: all 40 records in this repo's journal are `rubric-v1` and every
|
|
53
|
+
* one of them exited 1 as DRIFT under the rubric-v2 engine.
|
|
54
|
+
*
|
|
43
55
|
* Usage:
|
|
44
56
|
* eval-session.mjs [--session <id>] [--json] [--no-write]
|
|
45
57
|
* [--metrics-dir <path>] [--rubric <path>]
|
|
@@ -68,6 +80,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
68
80
|
const EXIT_OK = 0;
|
|
69
81
|
const EXIT_USER = 1;
|
|
70
82
|
const EXIT_SYSTEM = 2;
|
|
83
|
+
/** Non-standard, `--verify` only: the stored record predates the current rubric (#1400). */
|
|
84
|
+
const EXIT_VERSION_MISMATCH = 3;
|
|
71
85
|
|
|
72
86
|
function readPkgVersion() {
|
|
73
87
|
try {
|
|
@@ -103,6 +117,9 @@ EXIT CODES
|
|
|
103
117
|
1 user error (session not found, unknown run-id, record failed validation)
|
|
104
118
|
/ verify drift
|
|
105
119
|
2 system error (could not append the record to the eval journal)
|
|
120
|
+
3 verify version-mismatch: the stored record was scored under a different
|
|
121
|
+
rubric than ${RUBRIC_VERSION}, so it is not re-verifiable with this engine
|
|
122
|
+
(non-standard code — neither a match nor drift)
|
|
106
123
|
`;
|
|
107
124
|
|
|
108
125
|
function fail(exitCode, message) {
|
|
@@ -211,6 +228,49 @@ function runVerify(runId, values) {
|
|
|
211
228
|
fail(EXIT_USER, `--verify: run-id not found in eval journal: ${runId}`);
|
|
212
229
|
}
|
|
213
230
|
|
|
231
|
+
// #1400 — check the rubric BEFORE re-scoring. A record scored under another
|
|
232
|
+
// rubric answers a different pre-registered check set, so re-running the
|
|
233
|
+
// current engine over it produces a comparison nobody asked for. A record
|
|
234
|
+
// with no `rubric_version` at all is unattributable and counts as a mismatch.
|
|
235
|
+
const storedRubric = typeof stored.rubric_version === 'string' && stored.rubric_version
|
|
236
|
+
? stored.rubric_version
|
|
237
|
+
: null;
|
|
238
|
+
if (storedRubric !== RUBRIC_VERSION) {
|
|
239
|
+
// Deliberately NO diff of the dimensions the two rubrics share. Such a diff
|
|
240
|
+
// is technically possible but would carry almost no signal here. Measured
|
|
241
|
+
// 2026-09-19 over this repo's 40-record journal, replaying every record
|
|
242
|
+
// through `evaluateSession` + `diffDimensions`: all 40 differ on
|
|
243
|
+
// `process-safety` (its formula IS the v2 change) and all 40 gain a
|
|
244
|
+
// `present-in-fresh-only` `guard-friction`; restricted to the
|
|
245
|
+
// formula-unchanged dimensions, only 4 of 40 are byte-identical and the
|
|
246
|
+
// other 36 differ on `verification-evidence` / `gate-health` because the
|
|
247
|
+
// events they were scored from predate this journal's `events.jsonl`. So a
|
|
248
|
+
// "common dimensions" diff would re-report 90% of the journal as drift under
|
|
249
|
+
// a new name — the same misdirection in another shape (HR-101: a class that
|
|
250
|
+
// fires on ~90% of samples is a broken instrument, not a signal).
|
|
251
|
+
// Ceiling: holds while a rubric bump coincides with truncated evidence.
|
|
252
|
+
// Revisit-Trigger: the next rubric bump where every stored record's source
|
|
253
|
+
// events are still present — then a common-dimension diff carries signal.
|
|
254
|
+
if (values.json) {
|
|
255
|
+
writeStdoutLineSync(
|
|
256
|
+
JSON.stringify({
|
|
257
|
+
run_id: runId,
|
|
258
|
+
match: false,
|
|
259
|
+
verdict: 'version-mismatch',
|
|
260
|
+
stored_rubric_version: storedRubric,
|
|
261
|
+
engine_rubric_version: RUBRIC_VERSION,
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
} else {
|
|
265
|
+
writeStdoutLineSync(
|
|
266
|
+
`VERSION-MISMATCH: ${runId} was scored under ${storedRubric ?? '(no rubric_version)'}; ` +
|
|
267
|
+
`this engine scores ${RUBRIC_VERSION} — not re-verifiable with this engine ` +
|
|
268
|
+
'(not drift, not a match).',
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
process.exit(EXIT_VERSION_MISMATCH);
|
|
272
|
+
}
|
|
273
|
+
|
|
214
274
|
let fresh;
|
|
215
275
|
try {
|
|
216
276
|
fresh = evaluateSession({
|
|
@@ -236,7 +296,9 @@ function runVerify(runId, values) {
|
|
|
236
296
|
|
|
237
297
|
if (diffs.length === 0) {
|
|
238
298
|
if (values.json) {
|
|
239
|
-
writeStdoutLineSync(
|
|
299
|
+
writeStdoutLineSync(
|
|
300
|
+
JSON.stringify({ run_id: runId, match: true, verdict: 'match', dimensions: fresh.dimensions.length }),
|
|
301
|
+
);
|
|
240
302
|
} else {
|
|
241
303
|
writeStdoutLineSync(`MATCH: ${runId} re-evaluates identically across ${fresh.dimensions.length} dimension(s).`);
|
|
242
304
|
}
|
|
@@ -244,7 +306,7 @@ function runVerify(runId, values) {
|
|
|
244
306
|
}
|
|
245
307
|
|
|
246
308
|
if (values.json) {
|
|
247
|
-
writeStdoutLineSync(JSON.stringify({ run_id: runId, match: false, diffs }));
|
|
309
|
+
writeStdoutLineSync(JSON.stringify({ run_id: runId, match: false, verdict: 'drift', diffs }));
|
|
248
310
|
} else {
|
|
249
311
|
const out = [`DRIFT: ${runId} re-evaluation differs from the stored record:`];
|
|
250
312
|
for (const d of diffs) {
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* research/hardware-patterns.md`), via the same `vault-integration.vault-dir`
|
|
15
15
|
* resolution `scripts/archive-closed-prds.mjs` uses (findProjectRoot →
|
|
16
16
|
* CLAUDE.md/AGENTS.md → parseSessionConfig; host-resolved: SO_VAULT_DIR env >
|
|
17
|
-
* owner.yaml paths.vault-dir > committed
|
|
18
|
-
*
|
|
17
|
+
* owner.yaml vault-dirs: cwd match > owner.yaml paths.vault-dir > committed
|
|
18
|
+
* default, see `resolveVaultDir` in scripts/lib/config.mjs). Pass `--output <path>`
|
|
19
|
+
* to override.
|
|
19
20
|
*
|
|
20
21
|
* Idempotent: running without new data rewrites the same file byte-for-byte
|
|
21
22
|
* modulo the generated-at line.
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
rewriteLearnings,
|
|
59
60
|
CURRENT_ANONYMIZATION_VERSION,
|
|
60
61
|
} from './lib/learnings.mjs';
|
|
62
|
+
import { withLearningsLock } from './lib/learnings/io.mjs';
|
|
61
63
|
import { findProjectRoot, resolveInstructionFile, expandTilde } from './lib/common.mjs';
|
|
62
64
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
63
65
|
import { createSecretValueMasker } from './lib/secret-masker.mjs';
|
|
@@ -74,7 +76,8 @@ const DEFAULT_VAULT_SUBPATH = path.join('01-projects', 'session-orchestrator', '
|
|
|
74
76
|
* explicitly. Mirrors the vault-dir resolution `scripts/archive-closed-prds.mjs`
|
|
75
77
|
* uses: findProjectRoot → CLAUDE.md/AGENTS.md → parseSessionConfig →
|
|
76
78
|
* vault-integration.vault-dir (host-resolved: SO_VAULT_DIR env > owner.yaml
|
|
77
|
-
* paths.vault-dir > committed default
|
|
79
|
+
* vault-dirs: cwd match > owner.yaml paths.vault-dir > committed default, see
|
|
80
|
+
* `resolveVaultDir` in scripts/lib/config.mjs).
|
|
78
81
|
*
|
|
79
82
|
* @param {object} [opts]
|
|
80
83
|
* @param {string} [opts.repoRoot] — override for findProjectRoot() (tests).
|
|
@@ -634,6 +637,22 @@ function parseArgs(argv) {
|
|
|
634
637
|
* @returns {Promise<{promoted: number, skipped: number, flags: string[]}>}
|
|
635
638
|
*/
|
|
636
639
|
export async function promoteHwLearnings(opts) {
|
|
640
|
+
// The read → rewrite below runs under the store lock (#1447 point 8): an
|
|
641
|
+
// append landing between them would vanish in the rename. A dry run writes
|
|
642
|
+
// nothing and takes no lock. A lock not acquired throws LearningsLockError
|
|
643
|
+
// (nothing written), which the CLI reports as `export failed: …`, exit 1.
|
|
644
|
+
if (opts.dryRun) return promoteHwLearningsUnlocked(opts);
|
|
645
|
+
return withLearningsLock(opts.input, () => promoteHwLearningsUnlocked(opts));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Body of {@link promoteHwLearnings}; the caller holds the store lock or runs
|
|
650
|
+
* a dry run.
|
|
651
|
+
*
|
|
652
|
+
* @param {Parameters<typeof promoteHwLearnings>[0]} opts
|
|
653
|
+
* @returns {Promise<{promoted: number, skipped: number, flags: string[]}>}
|
|
654
|
+
*/
|
|
655
|
+
async function promoteHwLearningsUnlocked(opts) {
|
|
637
656
|
const repoRoot = opts.repoRoot;
|
|
638
657
|
const { entries, malformed } = await readLearnings(opts.input);
|
|
639
658
|
|