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
|
@@ -105,14 +105,14 @@
|
|
|
105
105
|
*/
|
|
106
106
|
|
|
107
107
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
108
|
-
|
|
109
|
-
if (!shouldRunHook('subagent-telemetry')) process.exit(0);
|
|
108
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
110
109
|
|
|
111
110
|
import fs from 'node:fs';
|
|
112
111
|
import path from 'node:path';
|
|
113
112
|
import { appendSubagent } from '../scripts/lib/subagents-schema.mjs';
|
|
114
113
|
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
115
114
|
import { resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
|
|
115
|
+
import { readTailWindow } from '../scripts/lib/tail-window.mjs';
|
|
116
116
|
|
|
117
117
|
// ---------------------------------------------------------------------------
|
|
118
118
|
// Constants
|
|
@@ -133,7 +133,16 @@ function jsonlPath() {
|
|
|
133
133
|
* Defense-in-depth byte ceiling for transcript reads (#624). The transcript path
|
|
134
134
|
* is harness-trusted, so this is not an exploitable vector — but the repo follows
|
|
135
135
|
* a bounded-read discipline: a pathologically large transcript is skipped (yields
|
|
136
|
-
* the
|
|
136
|
+
* the null-usage result) rather than read whole into memory.
|
|
137
|
+
*
|
|
138
|
+
* CEILING: a skipped transcript yields a stop record with found=true but null
|
|
139
|
+
* tokens and model; the session rollup counts it as an unpriced cost candidate
|
|
140
|
+
* (#1474) instead of reading it. Frequency per issue #1474 (2026-09-29): 2 of
|
|
141
|
+
* 10,482 subagent transcripts exceeded this size. REVISIT when oversized
|
|
142
|
+
* transcripts exceed ~0.1% of found stop records — count them from transcript
|
|
143
|
+
* sizes, because the ledger writes the same token-less record for an oversized,
|
|
144
|
+
* a usage-free and an unreadable transcript — then read large transcripts
|
|
145
|
+
* streamed instead of skipping them.
|
|
137
146
|
*/
|
|
138
147
|
const MAX_TRANSCRIPT_BYTES = 50 * 1024 * 1024; // ~50 MB
|
|
139
148
|
|
|
@@ -423,13 +432,23 @@ function readStdinJson() {
|
|
|
423
432
|
* Four buckets, not two (#1244 / schema_version 2): `input_tokens`,
|
|
424
433
|
* `cache_read_input_tokens`, `cache_creation_input_tokens` and `output_tokens`
|
|
425
434
|
* are accumulated SEPARATELY per deduped turn, because they are billed at three
|
|
426
|
-
* different rates. `model` is
|
|
427
|
-
*
|
|
428
|
-
*
|
|
435
|
+
* different rates. `model` is what makes a cost estimate possible at all
|
|
436
|
+
* downstream, so it names only a model that actually carried tokens (#1470): a
|
|
437
|
+
* kept turn is token-bearing when its four clamped buckets sum to > 0. Exactly
|
|
438
|
+
* one distinct `message.model` across the token-bearing turns → that model;
|
|
439
|
+
* zero-token turns of another model (a trailing `<synthetic>` turn) are ignored.
|
|
440
|
+
* Two or more, or a token-bearing turn that names no model → null, an honest
|
|
441
|
+
* unknown, with the token sums unchanged. No token-bearing turn at all → the
|
|
442
|
+
* last kept block that carries one (null when absent). Two or more models with
|
|
443
|
+
* every token-bearing turn named → `modelsUsage` carries the four buckets per
|
|
444
|
+
* model, so the session rollup can price each part; otherwise it is null.
|
|
429
445
|
*
|
|
430
446
|
* @param {string|undefined|null} transcriptPath — absolute path from stdin
|
|
431
447
|
* @returns {{ tokenInputUncached: number|null, tokenCacheRead: number|null,
|
|
432
|
-
* tokenCacheCreation: number|null, tokenOutput: number|null, model: string|null
|
|
448
|
+
* tokenCacheCreation: number|null, tokenOutput: number|null, model: string|null,
|
|
449
|
+
* modelsUsage: Array<{ model: string, token_input_uncached: number,
|
|
450
|
+
* token_cache_read: number, token_cache_creation: number,
|
|
451
|
+
* token_output: number }>|null }}
|
|
433
452
|
*/
|
|
434
453
|
function extractTranscriptUsage(transcriptPath) {
|
|
435
454
|
const nullResult = {
|
|
@@ -438,6 +457,7 @@ function extractTranscriptUsage(transcriptPath) {
|
|
|
438
457
|
tokenCacheCreation: null,
|
|
439
458
|
tokenOutput: null,
|
|
440
459
|
model: null,
|
|
460
|
+
modelsUsage: null,
|
|
441
461
|
};
|
|
442
462
|
try {
|
|
443
463
|
if (typeof transcriptPath !== 'string' || !transcriptPath.trim()) return nullResult;
|
|
@@ -523,22 +543,73 @@ function extractTranscriptUsage(transcriptPath) {
|
|
|
523
543
|
let tokenCacheRead = 0;
|
|
524
544
|
let tokenCacheCreation = 0;
|
|
525
545
|
let tokenOutput = 0;
|
|
546
|
+
/** Last non-null model over ALL kept turns — the zero-token fallback. */
|
|
526
547
|
let model = null;
|
|
548
|
+
/** Per-model bucket sums of the token-bearing kept turns (#1470). */
|
|
549
|
+
const billedModels = new Map();
|
|
550
|
+
/** A token-bearing turn that names no model: its price is unknowable. */
|
|
551
|
+
let unattributed = false;
|
|
552
|
+
// Per-turn clamp (#624): a turn's value counts ONLY when it is a non-negative
|
|
553
|
+
// integer. A poisoned value (negative, NaN, float like 10.5) contributes 0 so
|
|
554
|
+
// the good turns survive. An absent side contributes 0, not null.
|
|
555
|
+
const clamp = (value) => (Number.isInteger(value) && value >= 0 ? value : 0);
|
|
527
556
|
for (const { usage, model: turnModel } of kept) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
557
|
+
const inTok = clamp(usage.input_tokens);
|
|
558
|
+
const cacheRead = clamp(usage.cache_read_input_tokens);
|
|
559
|
+
const cacheCreation = clamp(usage.cache_creation_input_tokens);
|
|
560
|
+
const outTok = clamp(usage.output_tokens);
|
|
561
|
+
tokenInputUncached += inTok;
|
|
562
|
+
tokenCacheRead += cacheRead;
|
|
563
|
+
tokenCacheCreation += cacheCreation;
|
|
564
|
+
tokenOutput += outTok;
|
|
565
|
+
const billed = inTok + cacheRead + cacheCreation + outTok > 0;
|
|
566
|
+
if (turnModel === null) {
|
|
567
|
+
if (billed) unattributed = true;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
model = turnModel;
|
|
571
|
+
if (!billed) continue;
|
|
572
|
+
let part = billedModels.get(turnModel);
|
|
573
|
+
if (part === undefined) {
|
|
574
|
+
part = {
|
|
575
|
+
model: turnModel,
|
|
576
|
+
token_input_uncached: 0,
|
|
577
|
+
token_cache_read: 0,
|
|
578
|
+
token_cache_creation: 0,
|
|
579
|
+
token_output: 0,
|
|
580
|
+
};
|
|
581
|
+
billedModels.set(turnModel, part);
|
|
582
|
+
}
|
|
583
|
+
part.token_input_uncached += inTok;
|
|
584
|
+
part.token_cache_read += cacheRead;
|
|
585
|
+
part.token_cache_creation += cacheCreation;
|
|
586
|
+
part.token_output += outTok;
|
|
540
587
|
}
|
|
541
588
|
|
|
589
|
+
// Model attribution (#1470): only a token-bearing turn is an observation of
|
|
590
|
+
// its model; one distinct model → that model, two or more → null, none →
|
|
591
|
+
// the fallback above. A token-bearing turn that names no model also gives
|
|
592
|
+
// null: its tokens must not be priced at whichever other model was named.
|
|
593
|
+
// A mixed-model record carries its per-model sums as `modelsUsage`, which
|
|
594
|
+
// the session rollup prices part by part (#1470). Measured 2026-09-29 over
|
|
595
|
+
// 10,745 subagent transcripts under `~/.claude/projects`: 17 (0.16%)
|
|
596
|
+
// contained more than one model on token-bearing turns (0 of 917 in this
|
|
597
|
+
// repo), while 273 contained more than one model id only through zero-token
|
|
598
|
+
// `<synthetic>` turns, which this rule ignores. A model-less token-bearing
|
|
599
|
+
// turn gets no breakdown: its tokens belong to no part, so the record stays
|
|
600
|
+
// unpriced. The ledger now tells a mixed-model null (`models_usage`
|
|
601
|
+
// present) from a missing model (absent). The fallback also names the LAST
|
|
602
|
+
// model of a transcript whose turns all carry 0 tokens — measured 2026-09-29,
|
|
603
|
+
// 31 of 10,479 records, all `<synthetic>`; the session rollup prices a record
|
|
604
|
+
// whose four buckets are all 0 at $0 regardless of its model (#1474), so that
|
|
605
|
+
// name is harmless.
|
|
606
|
+
let modelsUsage = null;
|
|
607
|
+
if (unattributed) model = null;
|
|
608
|
+
else if (billedModels.size > 1) {
|
|
609
|
+
model = null;
|
|
610
|
+
modelsUsage = [...billedModels.values()];
|
|
611
|
+
} else if (billedModels.size === 1) [model] = billedModels.keys();
|
|
612
|
+
|
|
542
613
|
// The aggregates are guaranteed non-negative integers by per-turn clamping
|
|
543
614
|
// above (Σ of non-negative integers), so emit them directly.
|
|
544
615
|
return {
|
|
@@ -547,6 +618,7 @@ function extractTranscriptUsage(transcriptPath) {
|
|
|
547
618
|
tokenCacheCreation,
|
|
548
619
|
tokenOutput,
|
|
549
620
|
model,
|
|
621
|
+
modelsUsage,
|
|
550
622
|
};
|
|
551
623
|
} catch {
|
|
552
624
|
return nullResult;
|
|
@@ -593,25 +665,18 @@ function resolveSubagentTranscriptPath(parentTranscriptPath, agentId) {
|
|
|
593
665
|
* @returns {number|null} epoch-ms of the matching start, or null
|
|
594
666
|
*/
|
|
595
667
|
function findStartTimestampMs(filePath, agentId) {
|
|
596
|
-
let fd;
|
|
597
668
|
try {
|
|
598
669
|
if (typeof agentId !== 'string' || !agentId.trim()) return null;
|
|
599
670
|
if (!fs.existsSync(filePath)) return null;
|
|
600
671
|
|
|
601
|
-
const
|
|
602
|
-
if (size === 0) return null;
|
|
672
|
+
const window = readTailWindow(filePath, START_JOIN_TAIL_BYTES);
|
|
673
|
+
if (window.size === 0) return null;
|
|
603
674
|
|
|
604
|
-
const
|
|
605
|
-
const from = size - readLen;
|
|
606
|
-
const buf = Buffer.allocUnsafe(readLen);
|
|
607
|
-
fd = fs.openSync(filePath, 'r');
|
|
608
|
-
fs.readSync(fd, buf, 0, readLen, from);
|
|
609
|
-
|
|
610
|
-
const lines = buf.toString('utf8').split('\n');
|
|
675
|
+
const lines = window.text.split('\n');
|
|
611
676
|
// When the window does not cover the whole file, the first element is a
|
|
612
677
|
// record sliced mid-line (possibly mid-UTF-8-sequence). Drop it rather than
|
|
613
678
|
// feed a corrupt fragment to JSON.parse.
|
|
614
|
-
if (
|
|
679
|
+
if (window.cut) lines.shift();
|
|
615
680
|
|
|
616
681
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
617
682
|
const line = lines[i].trim();
|
|
@@ -631,10 +696,6 @@ function findStartTimestampMs(filePath, agentId) {
|
|
|
631
696
|
return null;
|
|
632
697
|
} catch {
|
|
633
698
|
return null;
|
|
634
|
-
} finally {
|
|
635
|
-
if (fd !== undefined) {
|
|
636
|
-
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
637
|
-
}
|
|
638
699
|
}
|
|
639
700
|
}
|
|
640
701
|
|
|
@@ -763,7 +824,7 @@ async function main() {
|
|
|
763
824
|
// NO fallback to input.transcript_path: that path is the parent session
|
|
764
825
|
// transcript, and reading it is the #949 defect (every stop inherited the
|
|
765
826
|
// parent's running totals). A phantom stop gets null — the honest value.
|
|
766
|
-
const { tokenInputUncached, tokenCacheRead, tokenCacheCreation, tokenOutput, model } =
|
|
827
|
+
const { tokenInputUncached, tokenCacheRead, tokenCacheCreation, tokenOutput, model, modelsUsage } =
|
|
767
828
|
subagentTranscriptFound
|
|
768
829
|
? extractTranscriptUsage(subagentTranscriptPath)
|
|
769
830
|
: {
|
|
@@ -772,6 +833,7 @@ async function main() {
|
|
|
772
833
|
tokenCacheCreation: null,
|
|
773
834
|
tokenOutput: null,
|
|
774
835
|
model: null,
|
|
836
|
+
modelsUsage: null,
|
|
775
837
|
};
|
|
776
838
|
|
|
777
839
|
// schema_version 2 (#1244): `token_input` is now BILLABLE PROMPT VOLUME —
|
|
@@ -787,11 +849,15 @@ async function main() {
|
|
|
787
849
|
}
|
|
788
850
|
if (tokenOutput !== null) record.token_output = tokenOutput;
|
|
789
851
|
|
|
790
|
-
// Model id (#1244) — null when the transcript exposes none
|
|
852
|
+
// Model id (#1244) — null when the transcript exposes none, or when its
|
|
853
|
+
// token-bearing turns are not attributable to one model (#1470). Cost is NOT
|
|
791
854
|
// computed here: pricing lives in scripts/lib/telemetry/pricing.mjs and is
|
|
792
855
|
// applied by the session rollup, so this hot-path hook keeps its import
|
|
793
856
|
// graph unchanged.
|
|
794
857
|
record.model = model;
|
|
858
|
+
// Per-model breakdown (#1470) — written only for a mixed-model record, so
|
|
859
|
+
// the rollup can price it part by part; absent on every other record.
|
|
860
|
+
if (modelsUsage !== null) record.models_usage = modelsUsage;
|
|
795
861
|
|
|
796
862
|
// Cost is best-effort / forward-compat (#624): the native transcript does
|
|
797
863
|
// NOT expose total_cost_usd today, so this is null in practice. No rate
|
|
@@ -815,5 +881,12 @@ async function main() {
|
|
|
815
881
|
await appendSubagent(jsonlPath(), record);
|
|
816
882
|
}
|
|
817
883
|
|
|
818
|
-
//
|
|
819
|
-
|
|
884
|
+
// Entry guard (#1393): run only as the node script the harness execs — a bare
|
|
885
|
+
// `import()` must run no handler and must not exit the importing process.
|
|
886
|
+
if (isMainModule(import.meta.url)) {
|
|
887
|
+
// Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
|
|
888
|
+
if (!shouldRunHook('subagent-telemetry')) process.exit(0);
|
|
889
|
+
|
|
890
|
+
// Exit 0 always — informational hook must never block Claude.
|
|
891
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
|
892
|
+
}
|
|
@@ -41,63 +41,7 @@ import { join } from 'node:path';
|
|
|
41
41
|
// consumer repo or a test tmp-dir.
|
|
42
42
|
import { pathMatchesPattern, findScopeFile } from '../scripts/lib/hardening.mjs';
|
|
43
43
|
import { withStagingFenceLock } from '../scripts/lib/session-lock.mjs';
|
|
44
|
-
|
|
45
|
-
const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
46
|
-
// #801: resolve wave-scope.json via the same precedence every other reader
|
|
47
|
-
// uses (.pi/.cursor/.codex/.claude — scope-gate.mjs findScopeFile). The prior
|
|
48
|
-
// hardcoded `.orchestrator/wave-scope.json` path was DEAD — the coordinator
|
|
49
|
-
// writes wave-scope.json to the state dir (.claude/ on Claude Code; see
|
|
50
|
-
// skills/wave-executor/wave-loop.md), so this guard never fired. findScopeFile
|
|
51
|
-
// returns null when no scope file exists at any precedence dir, preserving
|
|
52
|
-
// the "no active wave → exit 0" semantics below.
|
|
53
|
-
const scopePath = findScopeFile(repoRoot);
|
|
54
|
-
const fenceDir = join(repoRoot, '.orchestrator', 'staging-fence');
|
|
55
|
-
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Sub-mode B — allowedPaths check
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
const stagedOutput = execSync('git diff --cached --name-only', { encoding: 'utf8' });
|
|
61
|
-
const stagedFiles = stagedOutput.split('\n').filter(Boolean);
|
|
62
|
-
|
|
63
|
-
if (scopePath) {
|
|
64
|
-
let scope;
|
|
65
|
-
try {
|
|
66
|
-
scope = JSON.parse(readFileSync(scopePath, 'utf8'));
|
|
67
|
-
} catch (err) {
|
|
68
|
-
process.stderr.write(`wave-scope-commit-guard: failed to parse wave-scope.json: ${err.message}\n`);
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const allowedPaths = Array.isArray(scope.allowedPaths) ? scope.allowedPaths : [];
|
|
73
|
-
if (allowedPaths.length > 0) {
|
|
74
|
-
const violations = stagedFiles.filter(
|
|
75
|
-
(f) => !allowedPaths.some((pattern) => pathMatchesPattern(f, pattern)),
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if (violations.length > 0) {
|
|
79
|
-
process.stderr.write('✗ wave-scope-commit-guard: staged paths outside wave-scope.allowedPaths:\n');
|
|
80
|
-
for (const v of violations) process.stderr.write(` - ${v}\n`);
|
|
81
|
-
process.stderr.write('\nThese files were likely added by lint-staged eslint --fix / prettier --write.\n');
|
|
82
|
-
process.stderr.write('To proceed:\n');
|
|
83
|
-
process.stderr.write(' 1) git restore --staged <path> # for each foreign path\n');
|
|
84
|
-
process.stderr.write(' 2) git commit # retry\n');
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Sub-mode C — cross-agent staging-fence reconciliation (issue #552)
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
//
|
|
94
|
-
// Skip entirely when no fence dir exists OR no staged files. The fence dir
|
|
95
|
-
// only appears when a wave-agent invoked `git add` while SO_WAVE_AGENT=1.
|
|
96
|
-
// Manual / coordinator commits never write a fence file, so this branch
|
|
97
|
-
// short-circuits to exit 0 for them (AC5 safe-default).
|
|
98
|
-
if (!existsSync(fenceDir) || stagedFiles.length === 0) {
|
|
99
|
-
process.exit(0);
|
|
100
|
-
}
|
|
44
|
+
import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
101
45
|
|
|
102
46
|
// Resolve the "current" agent id from SO_WAVE_AGENT_ID (when set by the
|
|
103
47
|
// caller) or fall back to a PID-derived marker. The fence files themselves
|
|
@@ -105,11 +49,20 @@ if (!existsSync(fenceDir) || stagedFiles.length === 0) {
|
|
|
105
49
|
// fence entries (we cross-check against SIBLINGS, not ourselves).
|
|
106
50
|
const ownAgentId = process.env.SO_WAVE_AGENT_ID ?? null;
|
|
107
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Recorded by the writer when a staging command stages a set no path operand
|
|
54
|
+
* names (`git add -A`, `git add .`, `git add -u`). Overlaps everything.
|
|
55
|
+
* Mirrors ALL_PATHS_MARKER in hooks/pre-bash-staging-fence.mjs.
|
|
56
|
+
*/
|
|
57
|
+
const ALL_PATHS_MARKER = '*';
|
|
58
|
+
|
|
108
59
|
/**
|
|
109
60
|
* Build a regex that finds a staged path inside a `git add` command string.
|
|
110
61
|
* Word-boundary on both sides so `src/foo.ts` does not match `src/foo.ts.bak`.
|
|
111
62
|
* The path is escaped so glob metacharacters (`*`, `?`) and shell metas
|
|
112
63
|
* cannot be reinterpreted.
|
|
64
|
+
*
|
|
65
|
+
* LEGACY PATH ONLY since #1404 — see the fallback branch in findOverlaps.
|
|
113
66
|
*/
|
|
114
67
|
function pathRegex(p) {
|
|
115
68
|
const escaped = p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -117,8 +70,56 @@ function pathRegex(p) {
|
|
|
117
70
|
}
|
|
118
71
|
|
|
119
72
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
73
|
+
* Normalise a path token so reader and writer compare the same spelling.
|
|
74
|
+
*
|
|
75
|
+
* DELIBERATE DUPLICATE of the identically-named function in
|
|
76
|
+
* hooks/pre-bash-staging-fence.mjs: this guard is a husky pre-commit hook
|
|
77
|
+
* outside the Claude-Code hook import set, and the two sides must agree byte
|
|
78
|
+
* for byte. Change one, change both.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} raw
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function normalizeStagedPath(raw) {
|
|
84
|
+
let p = String(raw).trim();
|
|
85
|
+
while (p.startsWith('./')) p = p.slice(2);
|
|
86
|
+
p = p.replace(/\/{2,}/g, '/');
|
|
87
|
+
while (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1);
|
|
88
|
+
return p;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Does a path a sibling recorded overlap one of OUR staged files? Both sides
|
|
93
|
+
* arrive already normalised. A DIRECTORY entry overlaps every file beneath it
|
|
94
|
+
* (`src` overlaps `src/foo.ts`); the marker overlaps everything.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} fencePath
|
|
97
|
+
* @param {string} ourPath
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function pathsOverlap(fencePath, ourPath) {
|
|
101
|
+
if (fencePath === ALL_PATHS_MARKER) return true;
|
|
102
|
+
if (fencePath === ourPath) return true;
|
|
103
|
+
return ourPath.startsWith(`${fencePath}/`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Walk a single sibling fence file and return the staged paths a sibling
|
|
108
|
+
* agent also recorded an intent to stage.
|
|
109
|
+
*
|
|
110
|
+
* Two entry shapes are accepted:
|
|
111
|
+
* - #1404 shape `{ paths, command_hash, timestamp }` — path LISTS compared
|
|
112
|
+
* against our staged set.
|
|
113
|
+
* - LEGACY shape `{ command, timestamp }` — a fence file written by a
|
|
114
|
+
* pre-#1404 hook version. Live sessions on this host may hold such files
|
|
115
|
+
* RIGHT NOW, and dropping them would read as "no overlap" — silently
|
|
116
|
+
* weaker than before the change. So the old raw-command regex still runs
|
|
117
|
+
* for an entry that has `command` and no `paths`.
|
|
118
|
+
* NAMED CEILING (BV-004): this fallback exists only to span the sessions
|
|
119
|
+
* running across the upgrade. REVISIT TRIGGER — remove it one minor
|
|
120
|
+
* release after 5.3.0 (i.e. in 5.4.0), by which point no process started
|
|
121
|
+
* before the upgrade can still be writing fence files. The command text it
|
|
122
|
+
* reads is never PRINTED (issue #1404) — only the fact of the match is.
|
|
122
123
|
*/
|
|
123
124
|
function findOverlaps(fenceJsonPath, ourStaged) {
|
|
124
125
|
let body;
|
|
@@ -131,61 +132,157 @@ function findOverlaps(fenceJsonPath, ourStaged) {
|
|
|
131
132
|
if (!Array.isArray(body.staged_paths)) return [];
|
|
132
133
|
if (ownAgentId && body.agent_id === ownAgentId) return []; // skip self
|
|
133
134
|
|
|
135
|
+
const siblingAgent = body.agent_id ?? '<unknown>';
|
|
134
136
|
const matches = [];
|
|
135
137
|
for (const entry of body.staged_paths) {
|
|
138
|
+
if (Array.isArray(entry?.paths)) {
|
|
139
|
+
const hash = typeof entry.command_hash === 'string' ? entry.command_hash : '<no-hash>';
|
|
140
|
+
for (const raw of entry.paths) {
|
|
141
|
+
if (typeof raw !== 'string') continue;
|
|
142
|
+
const fencePath = normalizeStagedPath(raw);
|
|
143
|
+
for (const ours of ourStaged) {
|
|
144
|
+
if (pathsOverlap(fencePath, normalizeStagedPath(ours))) {
|
|
145
|
+
matches.push({ ourPath: ours, siblingAgent, fencePath, hash });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
136
152
|
const cmd = entry?.command;
|
|
137
153
|
if (typeof cmd !== 'string') continue;
|
|
138
154
|
for (const ours of ourStaged) {
|
|
139
155
|
if (pathRegex(ours).test(cmd)) {
|
|
140
|
-
matches.push({
|
|
156
|
+
matches.push({
|
|
157
|
+
ourPath: ours,
|
|
158
|
+
siblingAgent,
|
|
159
|
+
fencePath: '<legacy entry, pre-#1404>',
|
|
160
|
+
hash: '<legacy entry, pre-#1404>',
|
|
161
|
+
});
|
|
141
162
|
}
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
165
|
return matches;
|
|
145
166
|
}
|
|
146
167
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Both sub-modes, in the order the pre-commit hook needs them. Every statement
|
|
170
|
+
* here used to sit at module top level, which meant a bare `import()` of this
|
|
171
|
+
* file ran `git rev-parse` / `git diff --cached` and could exit the IMPORTING
|
|
172
|
+
* process with 1 (#1393). Moved into a function so the entry guard below can
|
|
173
|
+
* gate it; the logic and its exit codes are unchanged.
|
|
174
|
+
*/
|
|
175
|
+
async function main() {
|
|
176
|
+
const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
|
|
177
|
+
// #801: resolve wave-scope.json via the same precedence every other reader
|
|
178
|
+
// uses (.pi/.cursor/.codex/.claude — scope-gate.mjs findScopeFile). The prior
|
|
179
|
+
// hardcoded `.orchestrator/wave-scope.json` path was DEAD — the coordinator
|
|
180
|
+
// writes wave-scope.json to the state dir (.claude/ on Claude Code; see
|
|
181
|
+
// skills/wave-executor/wave-loop.md), so this guard never fired. findScopeFile
|
|
182
|
+
// returns null when no scope file exists at any precedence dir, preserving
|
|
183
|
+
// the "no active wave → exit 0" semantics below.
|
|
184
|
+
const scopePath = findScopeFile(repoRoot);
|
|
185
|
+
const fenceDir = join(repoRoot, '.orchestrator', 'staging-fence');
|
|
186
|
+
|
|
187
|
+
// -------------------------------------------------------------------------
|
|
188
|
+
// Sub-mode B — allowedPaths check
|
|
189
|
+
// -------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
const stagedOutput = execSync('git diff --cached --name-only', { encoding: 'utf8' });
|
|
192
|
+
const stagedFiles = stagedOutput.split('\n').filter(Boolean);
|
|
193
|
+
|
|
194
|
+
if (scopePath) {
|
|
195
|
+
let scope;
|
|
196
|
+
try {
|
|
197
|
+
scope = JSON.parse(readFileSync(scopePath, 'utf8'));
|
|
198
|
+
} catch (err) {
|
|
199
|
+
process.stderr.write(`wave-scope-commit-guard: failed to parse wave-scope.json: ${err.message}\n`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const allowedPaths = Array.isArray(scope.allowedPaths) ? scope.allowedPaths : [];
|
|
204
|
+
if (allowedPaths.length > 0) {
|
|
205
|
+
const violations = stagedFiles.filter(
|
|
206
|
+
(f) => !allowedPaths.some((pattern) => pathMatchesPattern(f, pattern)),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (violations.length > 0) {
|
|
210
|
+
process.stderr.write('✗ wave-scope-commit-guard: staged paths outside wave-scope.allowedPaths:\n');
|
|
211
|
+
for (const v of violations) process.stderr.write(` - ${v}\n`);
|
|
212
|
+
process.stderr.write('\nThese files were likely added by lint-staged eslint --fix / prettier --write.\n');
|
|
213
|
+
process.stderr.write('To proceed:\n');
|
|
214
|
+
process.stderr.write(' 1) git restore --staged <path> # for each foreign path\n');
|
|
215
|
+
process.stderr.write(' 2) git commit # retry\n');
|
|
216
|
+
process.exit(1);
|
|
164
217
|
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
);
|
|
168
|
-
} catch (err) {
|
|
169
|
-
// Lock acquisition failed — emit a warning but do NOT block the commit.
|
|
170
|
-
// The race-detection layer is opportunistic; a lock-acquire timeout is
|
|
171
|
-
// strictly less severe than blocking a legitimate commit on flaky FS.
|
|
172
|
-
process.stderr.write(
|
|
173
|
-
`⚠ wave-scope-commit-guard: staging-fence lock failed — ${err?.message ?? err}\n`,
|
|
174
|
-
);
|
|
175
|
-
process.exit(0);
|
|
176
|
-
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
177
220
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
221
|
+
// -------------------------------------------------------------------------
|
|
222
|
+
// Sub-mode C — cross-agent staging-fence reconciliation (issue #552)
|
|
223
|
+
// -------------------------------------------------------------------------
|
|
224
|
+
//
|
|
225
|
+
// Skip entirely when no fence dir exists OR no staged files. The fence dir
|
|
226
|
+
// only appears when a wave-agent invoked `git add` while SO_WAVE_AGENT=1.
|
|
227
|
+
// Manual / coordinator commits never write a fence file, so this branch
|
|
228
|
+
// short-circuits to exit 0 for them (AC5 safe-default).
|
|
229
|
+
if (!existsSync(fenceDir) || stagedFiles.length === 0) {
|
|
230
|
+
process.exit(0);
|
|
182
231
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
232
|
+
|
|
233
|
+
let overlaps = [];
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await withStagingFenceLock(
|
|
237
|
+
repoRoot,
|
|
238
|
+
async () => {
|
|
239
|
+
let entries;
|
|
240
|
+
try {
|
|
241
|
+
entries = readdirSync(fenceDir);
|
|
242
|
+
} catch {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
for (const name of entries) {
|
|
246
|
+
if (!name.endsWith('.json')) continue;
|
|
247
|
+
if (name.startsWith('.')) continue; // skip .commit.lock, tmp files
|
|
248
|
+
const overlapsFromFile = findOverlaps(join(fenceDir, name), stagedFiles);
|
|
249
|
+
overlaps = overlaps.concat(overlapsFromFile);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{ timeoutMs: 5000 },
|
|
253
|
+
);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
// Lock acquisition failed — emit a warning but do NOT block the commit.
|
|
256
|
+
// The race-detection layer is opportunistic; a lock-acquire timeout is
|
|
257
|
+
// strictly less severe than blocking a legitimate commit on flaky FS.
|
|
258
|
+
process.stderr.write(
|
|
259
|
+
`⚠ wave-scope-commit-guard: staging-fence lock failed — ${err?.message ?? err}\n`,
|
|
260
|
+
);
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (overlaps.length > 0) {
|
|
265
|
+
process.stderr.write('✗ wave-scope-commit-guard: staging-fence: cross-agent overlap detected:\n');
|
|
266
|
+
for (const o of overlaps) {
|
|
267
|
+
process.stderr.write(
|
|
268
|
+
` - ${o.ourPath} (sibling agent ${o.siblingAgent} recorded ${o.fencePath}, command ${o.hash})\n`,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
process.stderr.write('\nAnother wave-agent recorded a `git add` for one or more of your staged paths.\n');
|
|
272
|
+
process.stderr.write('To proceed:\n');
|
|
273
|
+
process.stderr.write(' 1) Coordinate with the sibling agent OR\n');
|
|
274
|
+
process.stderr.write(' 2) git restore --staged <path> # for each conflicting path, then retry\n');
|
|
275
|
+
process.stderr.write(' 3) git commit --no-verify # bypass (PSA-001/PSA-003 risk — operator opt-out)\n');
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
process.exit(0);
|
|
189
280
|
}
|
|
190
281
|
|
|
191
|
-
|
|
282
|
+
// Entry guard (#1393): run only as the node script `.husky/pre-commit` execs —
|
|
283
|
+
// a bare `import()` must run no git command and must not exit the importing
|
|
284
|
+
// process. No shouldRunHook gate here: this file is NOT registered in
|
|
285
|
+
// hooks/hooks.json, so the profile-gate never governed it.
|
|
286
|
+
if (isMainModule(import.meta.url)) {
|
|
287
|
+
await main();
|
|
288
|
+
}
|
package/monitors/monitors.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
{
|
|
3
3
|
"name": "ecosystem-health",
|
|
4
4
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/lib/ecosystem-health.mjs --watch",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Local state-file watcher: every 900 s it stats and reads ONE file, .orchestrator/metrics/ecosystem-health.jsonl under the plugin root, and emits one NDJSON line per change (appeared / disappeared / changed). No network, no CI or branch probing, no repo enumeration, no Session Config read. Nothing writes that file yet (0 writers, measured 2026-09-18), so every run emits `no-state-yet` and idles until SIGTERM — ceiling and revisit trigger at DEFAULT_INTERVAL_S in scripts/lib/ecosystem-health.mjs. Hung on session-start because the previous trigger (on-skill-invoke:ecosystem-health) was self-referential: it could only start when the ecosystem-health skill ran, and that skill has 0 recorded invocations fleet-wide, so the watcher never started once.",
|
|
6
6
|
"when": "on-skill-invoke:session-start"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
@@ -32,7 +32,7 @@ Only include if the plan changed:
|
|
|
32
32
|
|
|
33
33
|
- One table row per agent — no nested details
|
|
34
34
|
- Status is exactly one of: `done`, `partial`, `failed`
|
|
35
|
-
- File lists use short relative paths (e.g., `hooks/on-stop.
|
|
35
|
+
- File lists use short relative paths (e.g., `hooks/on-stop.mjs`)
|
|
36
36
|
- Quality gates are pass/fail — no ambiguity
|
|
37
37
|
- Adaptations only when deviating from plan — omit if on track
|
|
38
38
|
- Keep under 20 lines total
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "session-orchestrator",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "A repeatable Plan, Go, Close workflow for AI coding sessions: /session reads your repo and agrees the scope, /go runs the work in waves with a quality gate between each, /close verifies and commits. Runs on Claude Code, Codex CLI, Cursor and Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://session-orchestrator.com",
|
|
@@ -94,8 +94,8 @@
|
|
|
94
94
|
},
|
|
95
95
|
"overrides": {
|
|
96
96
|
"vite": "^8.0.16",
|
|
97
|
-
"fast-uri": "^3.1.
|
|
98
|
-
"brace-expansion": "^5.0.
|
|
97
|
+
"fast-uri": "^3.1.8",
|
|
98
|
+
"brace-expansion": "^5.0.12"
|
|
99
99
|
},
|
|
100
100
|
"license": "MIT",
|
|
101
101
|
"repository": {
|
package/pi/prompts/eval.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Use this skill to run an honest session-process evaluation (Standard v1, aiat-llm-eval/1.0) — score the last completed orchestrator session against the pre-registered rubric-
|
|
2
|
+
description: "Use this skill to run an honest session-process evaluation (Standard v1, aiat-llm-eval/1.0) — score the last completed orchestrator session against the pre-registered rubric-v2 dimensions, run /eval, evaluate this session, produce an eval report, or re-verify a stored eval run for reproducibility. Deterministic-first with an optional advisory LLM judge; never produces a global score."
|
|
3
3
|
argument-hint: "[--session <id>] [--no-write] [--verify <run-id>]"
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Fleet coordinator persona for sessions running side by side on one host: it measures resources, pipelines and merge readiness itself, writes conditions (\"Auflagen\") as files, starts headless runs behind slot and busy gates, and grants no approvals. Operator-only, invoke as /session-orchestrator:navigator ticker|status|start|fenster|handover."
|
|
3
|
+
argument-hint: "[ticker|status|start|fenster|handover]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /navigator
|
|
7
|
+
|
|
8
|
+
Use the Session Orchestrator skill definition at `skills/navigator/SKILL.md`.
|
|
9
|
+
|
|
10
|
+
Arguments: $@
|
|
11
|
+
|
|
12
|
+
Read that skill file and follow it exactly. When it references `$ARGUMENTS`, substitute the arguments above. Keep all Session Orchestrator platform fallbacks intact.
|