session-orchestrator 5.11.0 → 5.13.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/.claude-plugin/marketplace.json +12 -1
- package/.claude-plugin/plugin.json +4 -1
- package/.codex-plugin/agents/explorer.toml +1 -1
- package/.codex-plugin/agents/session-reviewer.toml +2 -2
- package/.codex-plugin/agents/wave-worker.toml +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +8 -0
- package/.orchestrator/policy/public-export.json +30 -0
- package/CHANGELOG.md +180 -3
- package/README.md +126 -117
- package/agents/analyst.md +7 -2
- package/agents/architect-reviewer.md +7 -2
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +9 -2
- package/agents/dialectic-deriver.md +9 -3
- package/agents/docs-writer.md +7 -2
- package/agents/eval-judge.md +7 -1
- package/agents/qa-strategist.md +7 -2
- package/agents/security-reviewer.md +9 -2
- package/agents/session-reviewer.md +18 -2
- package/agents/skill-applied-judge.md +7 -1
- package/agents/test-writer.md +9 -2
- package/agents/ui-developer.md +9 -2
- package/agents/ux-evaluator.md +6 -1
- package/commands/session.md +4 -2
- package/commands/templates-ack.md +89 -18
- package/docs/agent-authoring.md +30 -11
- package/docs/ci-setup.md +38 -2
- package/docs/codex-setup.md +33 -9
- package/docs/components.md +13 -2
- package/docs/cursor-setup.md +3 -3
- package/docs/events-schema.md +5 -4
- package/docs/evidence-candidates.md +63 -0
- package/docs/fleet-checkin-audit.md +7 -4
- package/docs/github-mirror-protection.md +150 -3
- package/docs/install.md +102 -25
- package/docs/instruction-delivery.md +1 -1
- package/docs/migration-v4.md +2 -2
- package/docs/mods.md +118 -0
- package/docs/owner-config-schema.md +1 -0
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/pm-skills-marketplace.md +4 -3
- package/docs/rule-authoring.md +24 -13
- package/docs/scope-collision-guard.md +2 -1
- package/docs/session-config-reference.md +55 -21
- package/docs/session-config-template.md +32 -24
- package/docs/sessions-ledger-repair.md +137 -0
- package/docs/vault-docs-architecture.md +1 -1
- package/hooks/_lib/hook-import-set.json +406 -73
- package/hooks/_lib/hook-root.mjs +301 -0
- package/hooks/_lib/lock-bootstrap.mjs +434 -4
- package/hooks/_lib/orchestrator-ignore.mjs +3 -0
- package/hooks/_lib/subagent-transcript.mjs +2 -2
- package/hooks/_lib/vcs-create-matcher.mjs +13 -17
- package/hooks/cwd-change-restore.mjs +5 -1
- package/hooks/enforce-commands.mjs +4 -3
- package/hooks/enforce-scope.mjs +11 -1
- package/hooks/hooks-codex.json +48 -2
- package/hooks/hooks.json +31 -31
- package/hooks/on-session-end.mjs +38 -15
- package/hooks/on-session-start.mjs +158 -59
- package/hooks/on-stop.mjs +11 -6
- package/hooks/post-bash-issue-budget-refund.mjs +6 -1
- package/hooks/post-bash-write-verify.mjs +8 -4
- package/hooks/post-edit-validate.mjs +45 -52
- package/hooks/post-subagent-discovery-validator.mjs +2 -2
- package/hooks/post-tool-batch-wave-signal.mjs +8 -4
- package/hooks/post-tool-failure-corrective-context.mjs +5 -1
- package/hooks/pre-bash-destructive-guard.mjs +220 -8
- package/hooks/pre-bash-issue-budget.mjs +6 -1
- package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
- package/hooks/pre-bash-staging-fence.mjs +258 -14
- package/hooks/pre-bash-templates-first.mjs +28 -27
- package/hooks/run-node.sh +1 -1
- package/hooks/skill-invocation-telemetry.mjs +15 -8
- package/hooks/subagent-telemetry.mjs +77 -14
- package/hooks/wave-scope-commit-guard.mjs +143 -14
- package/mods/so-hud/.claude-plugin/plugin.json +11 -0
- package/mods/so-hud/README.md +57 -0
- package/mods/so-hud/hooks/beacon-io.mjs +101 -0
- package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
- package/mods/so-hud/hooks/hooks.json +1 -0
- package/mods/so-hud/hooks/logic.ts +182 -0
- package/mods/so-hud/hooks/register.tsx +670 -0
- package/mods/so-hud/hooks/rules.ts +2998 -0
- package/mods/so-hud/tsconfig.json +8 -0
- package/mods/so-hud/types/index.d.ts +36 -0
- package/package.json +4 -3
- package/rules/_index.md +1 -1
- package/rules/always-on/ask-via-tool.md +1 -1
- package/rules/always-on/loop-and-monitor.md +17 -194
- package/rules/always-on/parallel-sessions.md +3 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +1 -1
- package/rules/opt-in-stack/security-web.md +5 -1
- package/scripts/agent-usage-census.mjs +79 -0
- package/scripts/apply-session-learnings.mjs +34 -6
- package/scripts/archive-closed-prds.mjs +21 -5
- package/scripts/autopilot.mjs +11 -5
- package/scripts/backfill-abandoned-sessions.mjs +13 -3
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +61 -6
- package/scripts/backfill-sessions.mjs +16 -2
- package/scripts/check-package-manager.mjs +26 -8
- package/scripts/check-sessions-integrity.mjs +4 -1
- package/scripts/ci/assert-vitest-green.mjs +3 -0
- package/scripts/ci/windows-smoke.mjs +201 -0
- package/scripts/close-precheck.mjs +55 -0
- package/scripts/dialectic-deriver.mjs +20 -12
- package/scripts/emit-event.mjs +2 -2
- package/scripts/emit-session.mjs +239 -20
- package/scripts/eval-session.mjs +17 -3
- package/scripts/export-hw-learnings.mjs +29 -2
- package/scripts/fleet-checkin-audit.mjs +8 -4
- package/scripts/fleet-checkin.mjs +73 -3
- package/scripts/gc-stale-worktrees.mjs +6 -1
- package/scripts/github-export.mjs +128 -0
- package/scripts/github-protection-audit.mjs +3 -1
- package/scripts/handover-queue-check.mjs +209 -0
- package/scripts/harness-audit.mjs +6 -4
- package/scripts/lib/agent-frontmatter.mjs +57 -10
- package/scripts/lib/agent-status.mjs +125 -7
- package/scripts/lib/agent-usage-census.mjs +394 -0
- package/scripts/lib/auto-dialectic.mjs +17 -10
- package/scripts/lib/auto-dream.mjs +22 -14
- package/scripts/lib/autopilot/loop.mjs +3 -1
- package/scripts/lib/autopilot/recent-runs.mjs +4 -2
- package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
- package/scripts/lib/autopilot/telemetry.mjs +5 -1
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/baseline-archetypes.mjs +10 -2
- package/scripts/lib/blocked-commands-policy.mjs +4 -3
- package/scripts/lib/build-live-signals.mjs +13 -9
- package/scripts/lib/ci-status-banner.mjs +329 -24
- package/scripts/lib/codex/plugin-contract.mjs +8 -0
- package/scripts/lib/cold-start-detector.mjs +5 -2
- package/scripts/lib/command-blocker.mjs +603 -43
- package/scripts/lib/common.mjs +3 -2
- package/scripts/lib/config/cross-repo.mjs +74 -7
- package/scripts/lib/config/host-paths.mjs +6 -2
- package/scripts/lib/config/remote-hosts.mjs +2 -2
- package/scripts/lib/config/section-extractor.mjs +65 -11
- package/scripts/lib/config/template-keys.mjs +153 -0
- package/scripts/lib/config/vault-integration.mjs +1 -0
- package/scripts/lib/config.mjs +3 -3
- package/scripts/lib/coordinator-snapshot.mjs +1 -1
- package/scripts/lib/discovery/triage-state.mjs +2 -1
- package/scripts/lib/dispatcher/rank.mjs +5 -7
- package/scripts/lib/ecosystem-health.mjs +2 -2
- package/scripts/lib/eval/engine.mjs +3 -2
- package/scripts/lib/eval/session-resolve.mjs +6 -2
- package/scripts/lib/eval/sink.mjs +4 -3
- package/scripts/lib/events-retention-banner.mjs +3 -2
- package/scripts/lib/events-schema.mjs +10 -0
- package/scripts/lib/events.mjs +23 -12
- package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
- package/scripts/lib/fleet-protocol.mjs +21 -0
- package/scripts/lib/gates/gate-full.mjs +172 -11
- package/scripts/lib/gates/gate-helpers.mjs +236 -23
- package/scripts/lib/git-config-drift.mjs +141 -7
- package/scripts/lib/github-export.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
- package/scripts/lib/handover-queue-marks.mjs +159 -0
- package/scripts/lib/hardware-pattern-detector.mjs +103 -13
- package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
- package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
- package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
- package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
- package/scripts/lib/host-identity.mjs +9 -5
- package/scripts/lib/http-request.mjs +64 -0
- package/scripts/lib/instruction-budget-guard.mjs +30 -22
- package/scripts/lib/io.mjs +6 -3
- package/scripts/lib/issue-budget-reconcile.mjs +3 -2
- package/scripts/lib/issue-budget.mjs +36 -7
- package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
- package/scripts/lib/learnings/io.mjs +289 -15
- package/scripts/lib/learnings/select.mjs +53 -4
- package/scripts/lib/learnings/store-role.mjs +209 -0
- package/scripts/lib/learnings/surface.mjs +46 -7
- package/scripts/lib/ledger-path.mjs +113 -0
- package/scripts/lib/maintenance-due-banner.mjs +63 -12
- package/scripts/lib/memory-banner.mjs +5 -6
- package/scripts/lib/memory-proposals/collector.mjs +5 -2
- package/scripts/lib/memory-proposals/sink.mjs +7 -4
- package/scripts/lib/memory-proposals/store.mjs +22 -5
- package/scripts/lib/mode-selector-accuracy.mjs +2 -1
- package/scripts/lib/no-follow-open.mjs +57 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
- package/scripts/lib/own-lock-root.mjs +95 -0
- package/scripts/lib/owner-interview.mjs +2 -1
- package/scripts/lib/owner-yaml.mjs +70 -7
- package/scripts/lib/platform.mjs +161 -9
- package/scripts/lib/plugin-update-banner.mjs +15 -11
- package/scripts/lib/pre-push-local-partial.mjs +117 -0
- package/scripts/lib/pre-push-remote-gate.mjs +151 -25
- package/scripts/lib/process-group.mjs +270 -34
- package/scripts/lib/project-hygiene.mjs +28 -0
- package/scripts/lib/quality-gate.mjs +33 -9
- package/scripts/lib/reconcile/backlog.mjs +29 -15
- package/scripts/lib/reconcile/engine.mjs +26 -2
- package/scripts/lib/reconcile/idempotency.mjs +65 -3
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
- package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
- package/scripts/lib/resource-probe/evaluate.mjs +32 -9
- package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
- package/scripts/lib/rule-loader.mjs +20 -10
- package/scripts/lib/scope-baseline.mjs +123 -8
- package/scripts/lib/scope-echo.mjs +5 -2
- package/scripts/lib/scope-gate.mjs +55 -20
- package/scripts/lib/session-close-backfill.mjs +75 -7
- package/scripts/lib/session-end/close-precheck.mjs +151 -0
- package/scripts/lib/session-end/phase-skip.mjs +8 -7
- package/scripts/lib/session-end/tail-runner.mjs +7 -2
- package/scripts/lib/session-end/vault-orphans.mjs +205 -0
- package/scripts/lib/session-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-invocation.mjs +14 -3
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +85 -12
- package/scripts/lib/session-schema/constants.mjs +9 -0
- package/scripts/lib/session-schema/filters.mjs +56 -1
- package/scripts/lib/session-schema/validator.mjs +15 -5
- package/scripts/lib/session-start-probes.mjs +149 -22
- package/scripts/lib/session-token-rollup.mjs +2 -1
- package/scripts/lib/session-work-trees.mjs +166 -0
- package/scripts/lib/sessions-canonical.mjs +61 -3
- package/scripts/lib/sessions-integrity-banner.mjs +17 -39
- package/scripts/lib/sessions-staleness-banner.mjs +8 -5
- package/scripts/lib/sh-quote.mjs +1 -0
- package/scripts/lib/skill-evidence-window.mjs +5 -1
- package/scripts/lib/skill-health/join.mjs +10 -9
- package/scripts/lib/skill-invocations-schema.mjs +13 -0
- package/scripts/lib/skill-judgments-schema.mjs +6 -2
- package/scripts/lib/snapshot-probe.mjs +414 -0
- package/scripts/lib/so-hud/collect.mjs +174 -0
- package/scripts/lib/so-hud/runtime.mjs +87 -0
- package/scripts/lib/spiral-carryover.mjs +8 -1
- package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
- package/scripts/lib/state-md/idle-reset.mjs +248 -0
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- package/scripts/lib/state-md/recommendations.mjs +26 -4
- package/scripts/lib/state-md-peer-guard.mjs +2 -0
- package/scripts/lib/state-md.mjs +31 -8
- package/scripts/lib/sunset/walker.mjs +4 -3
- package/scripts/lib/telemetry/consent.mjs +4 -2
- package/scripts/lib/telemetry/queue.mjs +3 -3
- package/scripts/lib/telemetry/sync.mjs +45 -9
- package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
- package/scripts/lib/tmux-layout/layouts.mjs +20 -3
- package/scripts/lib/ux-grill/paths.mjs +5 -1
- package/scripts/lib/validate/check-agents.mjs +37 -6
- package/scripts/lib/validate/check-component-paths.mjs +25 -1
- package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
- package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
- package/scripts/lib/validate/check-json-files.mjs +3 -1
- package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
- package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
- package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
- package/scripts/lib/validate/check-unwired-features.mjs +4 -2
- package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
- package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
- package/scripts/lib/validate/confidential-names.mjs +3 -1
- package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
- package/scripts/lib/vault-mirror/namespace.mjs +22 -9
- package/scripts/lib/vault-mirror/process.mjs +197 -65
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
- package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
- package/scripts/lib/vault-mirror/retention.mjs +2 -2
- package/scripts/lib/vault-mirror/utils.mjs +106 -13
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +34 -5
- package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
- package/scripts/lib/win-process-tree.mjs +137 -0
- package/scripts/lib/workspace.mjs +1 -1
- package/scripts/lib/worktree/constants.mjs +1 -1
- package/scripts/lib/worktree/lifecycle.mjs +1 -1
- package/scripts/lib/worktree/listing.mjs +1 -1
- package/scripts/lib/zx.mjs +50 -0
- package/scripts/mcp-server.mjs +16 -7
- package/scripts/mcp-server.sh +13 -83
- package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
- package/scripts/memory-propose.mjs +10 -3
- package/scripts/migrate-cold-start-seed.mjs +7 -0
- package/scripts/migrate-legacy-learnings.sh +22 -0
- package/scripts/migrate-sessions-jsonl.mjs +16 -2
- package/scripts/migrate-vault-paths.mjs +29 -13
- package/scripts/parse-config.mjs +71 -14
- package/scripts/print-learnings-index.mjs +25 -2
- package/scripts/release-session-lock.mjs +94 -7
- package/scripts/release.mjs +451 -24
- package/scripts/relocate-vault-corpus.mjs +11 -7
- package/scripts/repair-invalid-sessions.mjs +68 -3
- package/scripts/resolve-session-invocation.mjs +57 -8
- package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
- package/scripts/run-quality-gate.mjs +33 -7
- package/scripts/self-update.mjs +56 -7
- package/scripts/session-commit-files.mjs +133 -0
- package/scripts/site-numbers.mjs +52 -9
- package/scripts/snapshot-probe.mjs +156 -0
- package/scripts/so-hud.mjs +68 -0
- package/scripts/state-md-idle-reset.mjs +82 -0
- package/scripts/sweep-expired-learnings.mjs +54 -6
- package/scripts/sweep-expired-rules.mjs +4 -1
- package/scripts/validate-plugin.mjs +22 -0
- package/scripts/vault-integration-watcher.mjs +54 -6
- package/scripts/vault-mirror.mjs +9 -0
- package/scripts/vault-orphans.mjs +171 -0
- package/skills/_shared/fleet-protocol.md +166 -3
- package/skills/_shared/instruction-file-resolution.md +1 -1
- package/skills/_shared/monitor-patterns.md +44 -25
- package/skills/_shared/platform-tools.md +7 -0
- package/skills/_shared/state-ownership.md +5 -4
- package/skills/architecture/references/domain-model.md +1 -1
- package/skills/autopilot/SKILL.md +3 -2
- package/skills/bootstrap/SKILL.md +3 -1
- package/skills/bootstrap/_shared-template.md +4 -4
- package/skills/bootstrap/fast-template.md +1 -1
- package/skills/bootstrap/private-contract.md +1 -1
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
- package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
- package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
- package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
- package/skills/claude-md-drift-check/SKILL.md +3 -3
- package/skills/claude-md-drift-check/checker.mjs +61 -101
- package/skills/close/SKILL.md +20 -7
- package/skills/discovery/probes/docs-staleness.mjs +7 -5
- package/skills/discovery/probes/frontend-slop.mjs +7 -3
- package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
- package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
- package/skills/discovery/probes/vault-staleness.mjs +5 -3
- package/skills/discovery/probes-session.md +3 -0
- package/skills/discovery/probes-vault.md +2 -0
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eval/SKILL.md +3 -1
- package/skills/evolve/SKILL.md +2 -0
- package/skills/evolve/references/evolve-analyze-mode.md +3 -3
- package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
- package/skills/gitlab-ops/SKILL.md +7 -6
- package/skills/gitlab-portfolio/SKILL.md +2 -2
- package/skills/go/SKILL.md +13 -1
- package/skills/npm-publish/SKILL.md +5 -2
- package/skills/plan/SKILL.md +23 -10
- package/skills/plan/mode-feature.md +13 -8
- package/skills/plan/mode-new.md +6 -14
- package/skills/plan/prd-feature-template.md +19 -0
- package/skills/plan/prd-full-template.md +12 -5
- package/skills/plan/prd-premortem-panel.md +48 -0
- package/skills/plan/prd-reviewer-prompt.md +11 -3
- package/skills/plan/soul.md +1 -1
- package/skills/portfolio/SKILL.md +5 -6
- package/skills/quality-gates/SKILL.md +2 -4
- package/skills/reconcile/SKILL.md +27 -4
- package/skills/release/SKILL.md +16 -8
- package/skills/session-end/SKILL.md +97 -13
- package/skills/session-end/phase-3-2-docs-verification.md +1 -0
- package/skills/session-end/phase-3-6-tail.md +11 -7
- package/skills/session-end/phase-3-7a-recommendations.md +9 -2
- package/skills/session-end/plan-verification.md +16 -1
- package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +8 -0
- package/skills/session-end/session-metrics-write.md +7 -4
- package/skills/session-plan/SKILL.md +5 -3
- package/skills/session-start/SKILL.md +10 -4
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- package/skills/session-start/phase-7-5-mode-selector.md +3 -1
- package/skills/session-start/phase-8-5-express-path.md +4 -3
- package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
- package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
- 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 +3 -2
- package/skills/tmux-layout/SKILL.md +2 -2
- package/skills/vault-sync/validator.mjs +3 -2
- package/skills/wave-executor/SKILL.md +5 -4
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
- package/skills/wave-executor/references/wave-loop-review.md +13 -5
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
- package/AGENTS.md +0 -207
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -29,23 +29,26 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
32
|
-
import { writeSync } from 'node:fs';
|
|
32
|
+
import { existsSync, writeSync } from 'node:fs';
|
|
33
33
|
import { execFile } from 'node:child_process';
|
|
34
34
|
import { promisify } from 'node:util';
|
|
35
35
|
import { randomUUID } from 'node:crypto';
|
|
36
|
+
import { hostname } from 'node:os';
|
|
36
37
|
import path from 'node:path';
|
|
37
38
|
|
|
38
39
|
import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
39
40
|
import { ensureOrchestratorIgnore } from './_lib/orchestrator-ignore.mjs';
|
|
41
|
+
import { resolveSessionStartRoot } from './_lib/hook-root.mjs';
|
|
40
42
|
|
|
41
43
|
import { emitEvent, eventsFilePath } from '../scripts/lib/events.mjs';
|
|
42
|
-
import { readNavigatorLease } from '../scripts/lib/fleet-protocol.mjs';
|
|
44
|
+
import { readNavigatorLease, fleetIntegrationEnabled } from '../scripts/lib/fleet-protocol.mjs';
|
|
43
45
|
import { maybeRotate } from '../scripts/lib/events-rotation.mjs';
|
|
44
46
|
import { readConfigFile, parseSessionConfig } from '../scripts/lib/config.mjs';
|
|
45
|
-
import { getPlatform, resolveProjectDir } from '../scripts/lib/platform.mjs';
|
|
47
|
+
import { getPlatform, resolveProjectDir, resolveSessionRoot } from '../scripts/lib/platform.mjs';
|
|
46
48
|
import {
|
|
47
49
|
registerSelf,
|
|
48
50
|
detectPeers,
|
|
51
|
+
countRecentStarts,
|
|
49
52
|
sweepZombies,
|
|
50
53
|
logSweepEvent,
|
|
51
54
|
repoPathHash,
|
|
@@ -58,8 +61,9 @@ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
|
|
|
58
61
|
// SSOT for the "is this a re-entry into the same logical session?" question
|
|
59
62
|
// (#1091). Defined in the lock-bootstrap leaf module, which this hook already
|
|
60
63
|
// loads, so the preservation branch below and the lock force-refresh gate can
|
|
61
|
-
// never drift apart.
|
|
62
|
-
|
|
64
|
+
// never drift apart. readClaudePid() is the process witness current-session.json
|
|
65
|
+
// carries next to the raw id (#1746).
|
|
66
|
+
import { SAME_LOGICAL_SESSION_SOURCES, readClaudePid } from './_lib/lock-bootstrap.mjs';
|
|
63
67
|
|
|
64
68
|
const execFileAsync = promisify(execFile);
|
|
65
69
|
|
|
@@ -187,14 +191,20 @@ function supersessionMarker(peer) {
|
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
/**
|
|
190
|
-
* Queue the ONE `additionalContext` string
|
|
191
|
-
*
|
|
192
|
-
*
|
|
194
|
+
* Queue text for the ONE `additionalContext` string of the single end-of-hook
|
|
195
|
+
* flush. Two writers exist (the consent nudge, the #1635 lead notice) and both
|
|
196
|
+
* are needed by the model, so a second text is appended on its own line; an
|
|
197
|
+
* identical text is not repeated. Every character costs context on every
|
|
198
|
+
* session start, so writers stay terse and conditional.
|
|
193
199
|
* @param {string|null|undefined} text
|
|
194
200
|
*/
|
|
195
201
|
function setAdditionalContext(text) {
|
|
196
202
|
if (typeof text !== 'string' || text.length === 0) return;
|
|
197
|
-
pendingAdditionalContext
|
|
203
|
+
if (typeof pendingAdditionalContext !== 'string' || pendingAdditionalContext.length === 0) {
|
|
204
|
+
pendingAdditionalContext = text;
|
|
205
|
+
} else if (!pendingAdditionalContext.split('\n').includes(text)) {
|
|
206
|
+
pendingAdditionalContext = `${pendingAdditionalContext}\n${text}`;
|
|
207
|
+
}
|
|
198
208
|
}
|
|
199
209
|
|
|
200
210
|
/**
|
|
@@ -241,14 +251,16 @@ function flushBanner() {
|
|
|
241
251
|
// ---------------------------------------------------------------------------
|
|
242
252
|
|
|
243
253
|
/**
|
|
244
|
-
* Run a git command in cwd; return trimmed stdout. Returns null on failure
|
|
254
|
+
* Run a git command in cwd; return trimmed stdout. Returns null on failure
|
|
255
|
+
* (including a timeout, when `timeoutMs` is given).
|
|
245
256
|
* @param {string[]} args
|
|
246
257
|
* @param {string} cwd
|
|
258
|
+
* @param {number} [timeoutMs] — kill the git child after this many ms (0 = no limit)
|
|
247
259
|
* @returns {Promise<string|null>}
|
|
248
260
|
*/
|
|
249
|
-
async function gitOutput(args, cwd) {
|
|
261
|
+
async function gitOutput(args, cwd, timeoutMs = 0) {
|
|
250
262
|
try {
|
|
251
|
-
const { stdout } = await execFileAsync('git', args, { cwd });
|
|
263
|
+
const { stdout } = await execFileAsync('git', args, { cwd, timeout: timeoutMs });
|
|
252
264
|
return stdout.trim();
|
|
253
265
|
} catch {
|
|
254
266
|
return null;
|
|
@@ -316,25 +328,27 @@ export async function readColdStartConfig(projectRoot) {
|
|
|
316
328
|
}
|
|
317
329
|
|
|
318
330
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
331
|
+
* Start-wave rule (#1650): WARN when at least START_WAVE_MIN foreign registry
|
|
332
|
+
* entries started within the last START_WAVE_WINDOW_MIN minutes.
|
|
333
|
+
*
|
|
334
|
+
* Why not `peer_count >= concurrent-sessions-warn` (the previous WARN trigger):
|
|
335
|
+
* that is a permanent state, not an event. Measured 2026-10-17 08:08Z/08:09Z
|
|
336
|
+
* over `orchestrator.session.started` since 2026-08-24, dedup session_id +
|
|
337
|
+
* timestamp, per `host_class`: peer_count >= 5 fired on 880/1305 = 67.4% (M4)
|
|
338
|
+
* and 255/991 = 25.7% (M5) of starts — a broken instrument per HR-101.
|
|
339
|
+
* This rule, reconstructed from the start timestamps themselves, fires on
|
|
340
|
+
* 35/1305 = 2.7% (M4) and 52/991 = 5.2% (M5). Neighbouring candidates:
|
|
341
|
+
* >=3 in 2 min 4.0% / 7.1%, >=5 in 2 min 1.7% / 3.3%, >=3 in 5 min 6.3% / 10.7%.
|
|
342
|
+
* 5 min was chosen over 2 min so a wave spread over a few minutes still shows.
|
|
343
|
+
*
|
|
344
|
+
* Ceiling (BV-004): fixed constants, no config key; the reconstruction is an
|
|
345
|
+
* upper bound (sessions that ended inside the window leave the registry).
|
|
346
|
+
* Revisit when either host's firing rate on `recent_starts_count >= 5` in the
|
|
347
|
+
* event ledger exceeds 10%, or a start wave that hurt was not flagged.
|
|
348
|
+
* Report only — this single soft signal caps nothing (HR-104).
|
|
322
349
|
*/
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
for (const name of ['CLAUDE.md', 'AGENTS.md']) {
|
|
326
|
-
try {
|
|
327
|
-
const raw = await readFile(path.join(projectRoot, name), 'utf8');
|
|
328
|
-
// Match `concurrent-sessions-warn: <int>` inside resource-thresholds or top-level.
|
|
329
|
-
const m = raw.match(/^\s*concurrent-sessions-warn:\s*(\d+)\b/im);
|
|
330
|
-
if (m) {
|
|
331
|
-
const n = parseInt(m[1], 10);
|
|
332
|
-
if (!Number.isNaN(n) && n > 0) return n;
|
|
333
|
-
}
|
|
334
|
-
} catch { /* file missing is fine */ }
|
|
335
|
-
}
|
|
336
|
-
return DEFAULT;
|
|
337
|
-
}
|
|
350
|
+
const START_WAVE_WINDOW_MIN = 5;
|
|
351
|
+
const START_WAVE_MIN = 5;
|
|
338
352
|
|
|
339
353
|
/**
|
|
340
354
|
* Non-blocking stdin read for Claude Code hook payload. Returns parsed JSON or
|
|
@@ -372,8 +386,13 @@ async function readStdinJson(timeoutMs = 500) {
|
|
|
372
386
|
* Codex/Cursor may not).
|
|
373
387
|
*
|
|
374
388
|
* Best-effort: any persistence failure is swallowed (hook must remain non-blocking).
|
|
389
|
+
*
|
|
390
|
+
* @param {object|null} input — the parsed SessionStart payload.
|
|
391
|
+
* @param {string} projectRoot
|
|
392
|
+
* @param {number|null} [claudePid=null] — readClaudePid(); recorded with the
|
|
393
|
+
* host as the writer's process witness (#1746).
|
|
375
394
|
*/
|
|
376
|
-
async function resolveSessionId(input, projectRoot) {
|
|
395
|
+
async function resolveSessionId(input, projectRoot, claudePid = null) {
|
|
377
396
|
const nativeSource = typeof input?.source === 'string' && input.source.length > 0
|
|
378
397
|
? input.source
|
|
379
398
|
: null;
|
|
@@ -477,9 +496,12 @@ async function resolveSessionId(input, projectRoot) {
|
|
|
477
496
|
// (`resume` was ABSENT from hooks.json until #1091, so on Claude Code this
|
|
478
497
|
// hook never ran on a native resume), `startup|reload|new|resume|fork` on
|
|
479
498
|
// pi, and every source on Cursor (empty matcher). Several of those sources
|
|
480
|
-
// are re-entries into the SAME logical session.
|
|
481
|
-
//
|
|
482
|
-
//
|
|
499
|
+
// are re-entries into the SAME logical session. The `semantic_session_id`
|
|
500
|
+
// (branch+date+mode+n) is meant to stay stable across them. The raw UUID
|
|
501
|
+
// `session_id` does not behave alike per source: on Claude Code `compact`
|
|
502
|
+
// KEEPS it (12/12 compact events in events.jsonl carry
|
|
503
|
+
// `resume_linkage: raw-id`, measured 2026-10-14), while `clear` showed
|
|
504
|
+
// 6/6 `resume_linkage: none`. Whether Claude Code PRESERVES the raw
|
|
483
505
|
// `session_id` across a native resume is what #1091 measures via
|
|
484
506
|
// `native_source` + `resume_linkage` on `orchestrator.session.started` —
|
|
485
507
|
// nothing here ASSUMES either answer, it only reacts to the raw id it can
|
|
@@ -548,6 +570,11 @@ async function resolveSessionId(input, projectRoot) {
|
|
|
548
570
|
pid: process.pid,
|
|
549
571
|
source,
|
|
550
572
|
timestamp: new Date().toISOString(),
|
|
573
|
+
// #1746 — `pid` above is this short-lived hook process, no witness of
|
|
574
|
+
// anything after it exits. The Claude Code process survives /clear, so
|
|
575
|
+
// the next run can tell its own predecessor from a peer that started
|
|
576
|
+
// later in this checkout. Absent without a readable CLAUDE_PID.
|
|
577
|
+
...(typeof claudePid === 'number' ? { claude_pid: claudePid, host: hostname() } : {}),
|
|
551
578
|
// Spread AFTER the base fields so the preserved high-water marks survive
|
|
552
579
|
// (and never clobber the identity fields above).
|
|
553
580
|
...preserved,
|
|
@@ -565,12 +592,18 @@ async function resolveSessionId(input, projectRoot) {
|
|
|
565
592
|
mode: normalizedMode,
|
|
566
593
|
nativeSource,
|
|
567
594
|
resumeLinkage,
|
|
568
|
-
// The raw session_id
|
|
569
|
-
//
|
|
570
|
-
//
|
|
595
|
+
// The raw session_id the LAST session start in this checkout recorded, or
|
|
596
|
+
// null. Every session start rewrites the file, so it is our own previous
|
|
597
|
+
// run only when no peer started here since (#1746). Surfaced (rather than
|
|
598
|
+
// kept local) because bootstrapLock() needs it as the third conjunct of
|
|
599
|
+
// the #1091 F2 force-refresh — see the call site in main().
|
|
571
600
|
predecessorSessionId: typeof prev?.session_id === 'string' && prev.session_id.length > 0
|
|
572
601
|
? prev.session_id
|
|
573
602
|
: null,
|
|
603
|
+
// #1746 — the process witness that writer recorded, unvalidated: the
|
|
604
|
+
// consumer compares it with isSameClaudeProcess(), which admits only an
|
|
605
|
+
// exact pid match on this host.
|
|
606
|
+
predecessorProcess: prev ? { claude_pid: prev.claude_pid, host: prev.host } : null,
|
|
574
607
|
};
|
|
575
608
|
}
|
|
576
609
|
|
|
@@ -780,7 +813,14 @@ function maybeQueueConsentNudge() {
|
|
|
780
813
|
// ---------------------------------------------------------------------------
|
|
781
814
|
|
|
782
815
|
async function main() {
|
|
783
|
-
|
|
816
|
+
// #1605 — the root depends on the payload `source`/`cwd`/`session_id`, so the
|
|
817
|
+
// optional stdin payload is read BEFORE the first writer below. Best-effort:
|
|
818
|
+
// readStdinJson resolves null on a closed/empty/unparseable stdin and caps
|
|
819
|
+
// itself at 500 ms. The rule (entered worktree only for clear/compact/resume,
|
|
820
|
+
// only a verified linked worktree, never over another live session's state)
|
|
821
|
+
// lives in hooks/_lib/hook-root.mjs, beside the readers' own-lock lookup.
|
|
822
|
+
const input = await readStdinJson();
|
|
823
|
+
const projectRoot = await resolveSessionStartRoot(input, resolveProjectDir());
|
|
784
824
|
|
|
785
825
|
// #1515 Pkt 5 — before this hook's own writers (host.json, session.lock,
|
|
786
826
|
// events.jsonl) run: an un-bootstrapped repo gets `.orchestrator/.gitignore`
|
|
@@ -788,11 +828,6 @@ async function main() {
|
|
|
788
828
|
// pay two existsSync and nothing else. Never throws.
|
|
789
829
|
await ensureOrchestratorIgnore(projectRoot);
|
|
790
830
|
|
|
791
|
-
// Read optional stdin payload in parallel with git info so we don't stall
|
|
792
|
-
// the hook. Both are best-effort and the promise races against a 500 ms cap
|
|
793
|
-
// inside readStdinJson itself.
|
|
794
|
-
const stdinPromise = readStdinJson();
|
|
795
|
-
|
|
796
831
|
// Resolve project name: basename of git toplevel, falling back to cwd basename.
|
|
797
832
|
const topLevel = await gitOutput(['rev-parse', '--show-toplevel'], projectRoot);
|
|
798
833
|
const projectName = topLevel
|
|
@@ -809,6 +844,24 @@ async function main() {
|
|
|
809
844
|
const rawHeadSha = await gitOutput(['rev-parse', 'HEAD'], projectRoot);
|
|
810
845
|
const headSha = rawHeadSha && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(rawHeadSha) ? rawHeadSha : null;
|
|
811
846
|
|
|
847
|
+
// #1668 — the upstream state at start, so emit-session can tell a stale
|
|
848
|
+
// head_sha (checkout behind its upstream: a later pull drags foreign commits
|
|
849
|
+
// into `<head_sha>..HEAD`) from a current one. Local refs only — no fetch, no
|
|
850
|
+
// network — and bounded at 1 s per call. No upstream configured, a timeout or
|
|
851
|
+
// a non-numeric count → BOTH keys omitted (not measured), never 0.
|
|
852
|
+
let upstreamSha = null;
|
|
853
|
+
let behind = null;
|
|
854
|
+
if (headSha) {
|
|
855
|
+
const [rawUpstream, rawBehind] = await Promise.all([
|
|
856
|
+
gitOutput(['rev-parse', '--verify', '--quiet', '@{u}^{commit}'], projectRoot, 1000),
|
|
857
|
+
gitOutput(['rev-list', '--count', 'HEAD..@{u}'], projectRoot, 1000),
|
|
858
|
+
]);
|
|
859
|
+
if (rawUpstream && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(rawUpstream) && rawBehind && /^\d+$/.test(rawBehind)) {
|
|
860
|
+
upstreamSha = rawUpstream;
|
|
861
|
+
behind = Number(rawBehind);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
812
865
|
// v3.1.0 env-aware banner (opt-out via enable-host-banner: false in Session Config).
|
|
813
866
|
// The ask-via-tool nudge rides the same opt-out flag — both are coordinator
|
|
814
867
|
// reminders shown at session start; users who silence one expect silence.
|
|
@@ -817,15 +870,23 @@ async function main() {
|
|
|
817
870
|
// null on its own error paths, which would silently re-open the nudge.
|
|
818
871
|
const hostBannerEnabled = await isHostBannerEnabled(projectRoot);
|
|
819
872
|
let bannerData = null;
|
|
873
|
+
// #1559: a git repository with no bootstrap.lock — neither at its toplevel
|
|
874
|
+
// nor under projectRoot (a bootstrapped sub-project) — has one first step,
|
|
875
|
+
// /bootstrap; /go and /close do not work before it. Judged at the toplevel
|
|
876
|
+
// too, so a session started in a subdirectory of a bootstrapped repo is not
|
|
877
|
+
// told to bootstrap again; outside a git repository there is nothing to set up.
|
|
878
|
+
const needsBootstrap = Boolean(topLevel)
|
|
879
|
+
&& !existsSync(path.join(topLevel, '.orchestrator', 'bootstrap.lock'))
|
|
880
|
+
&& !existsSync(path.join(projectRoot, '.orchestrator', 'bootstrap.lock'));
|
|
820
881
|
if (hostBannerEnabled) {
|
|
821
882
|
bannerData = await emitHostBanner(projectRoot);
|
|
822
|
-
//
|
|
823
|
-
//
|
|
824
|
-
//
|
|
825
|
-
//
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
883
|
+
// AUQ-001 in plain words (#1559): name the command when nothing is
|
|
884
|
+
// blocked, read available answers from config / STATE.md / git, and ask
|
|
885
|
+
// only when blocked — then with the question tool, never as a prose list.
|
|
886
|
+
// Before bootstrap, the setup step replaces it as the first action.
|
|
887
|
+
pushBanner(needsBootstrap
|
|
888
|
+
? 'ℹ New here? Start with /bootstrap — it sets up this repo for session-orchestrator.'
|
|
889
|
+
: '🎯 Next step: name the command (/go, /close) when nothing is blocked; read answers from config, STATE.md or git; when blocked, ask with the question tool, never as a prose list.');
|
|
829
890
|
}
|
|
830
891
|
|
|
831
892
|
// F1.3 cold-start abandonment fix (PRD 2026-05-21). Emit a one-shot
|
|
@@ -864,13 +925,18 @@ async function main() {
|
|
|
864
925
|
|
|
865
926
|
// v3.1.0 multi-session registry (#168). All steps best-effort — failures
|
|
866
927
|
// must never break the hook, which is informational-only.
|
|
867
|
-
const input = await stdinPromise;
|
|
868
928
|
// #1091 — the native SessionStart `source` (Claude Code: startup|resume|
|
|
869
929
|
// clear|compact), plus the linkage decision resolveSessionId() derived from
|
|
870
930
|
// it (see SAME_LOGICAL_SESSION_SOURCES). Both are surfaced on the event so
|
|
871
931
|
// #1091 gets its measurement instead of an inference.
|
|
872
|
-
|
|
873
|
-
|
|
932
|
+
// #1641: the Claude Code process survives /clear while the raw id and the
|
|
933
|
+
// semantic label are minted anew — its pid is the witness that a live lock,
|
|
934
|
+
// or the current-session.json entry (#1746), is our own predecessor's.
|
|
935
|
+
// Admitted only as this hook's direct parent: a Codex child of a Claude
|
|
936
|
+
// session inherits the variable.
|
|
937
|
+
const claudePid = readClaudePid();
|
|
938
|
+
const { sessionId, semanticSessionId, mode, nativeSource, resumeLinkage, predecessorSessionId, predecessorProcess } =
|
|
939
|
+
await resolveSessionId(input, projectRoot, claudePid);
|
|
874
940
|
// getPlatform() ALREADY implements the SO_PLATFORM override as step 1 of its
|
|
875
941
|
// precedence — and, unlike a bare `??`, it validates the value against the
|
|
876
942
|
// four-platform allowlist and trims it. The former `process.env.SO_PLATFORM ??
|
|
@@ -897,12 +963,23 @@ async function main() {
|
|
|
897
963
|
mode,
|
|
898
964
|
ttlHours: 4,
|
|
899
965
|
nativeSource,
|
|
900
|
-
// The raw session_id
|
|
966
|
+
// The raw session_id the last session start in this checkout recorded —
|
|
967
|
+
// our own previous run unless a peer started here since. It is the
|
|
901
968
|
// third conjunct of the same-logical-session force-refresh (#1091 F2): a
|
|
902
969
|
// semantic-label match alone is collidable on one host (#1066), so the
|
|
903
970
|
// live lock must ALSO carry our predecessor's raw id before we take it
|
|
904
971
|
// over. `prev` is read above, before the semantic id is minted.
|
|
905
972
|
predecessorSessionId,
|
|
973
|
+
// #1746: who wrote that raw id — the lead yield trusts it as "our own
|
|
974
|
+
// predecessor" only when this same Claude process did.
|
|
975
|
+
predecessorProcess,
|
|
976
|
+
claudePid,
|
|
977
|
+
// #1635: a fleet lead holds this main checkout — no lock was taken; the
|
|
978
|
+
// model and the operator are told to work in a worktree instead.
|
|
979
|
+
onNotice: (context, banner) => {
|
|
980
|
+
setAdditionalContext(context);
|
|
981
|
+
pushBanner(banner);
|
|
982
|
+
},
|
|
906
983
|
});
|
|
907
984
|
} catch { /* hook must remain non-blocking */ }
|
|
908
985
|
|
|
@@ -965,6 +1042,12 @@ async function main() {
|
|
|
965
1042
|
platform,
|
|
966
1043
|
mode,
|
|
967
1044
|
hostClass: bannerData?.host?.host_class ?? null,
|
|
1045
|
+
// #1605 — the pointer the readers (Stop, PostToolBatch, SessionEnd, the
|
|
1046
|
+
// current-session.json side writers) follow to THIS session's lock,
|
|
1047
|
+
// whatever their cwd; and the working copy, so a later relocation
|
|
1048
|
+
// into it by another session sees this one (B4). Stop refreshes it.
|
|
1049
|
+
sessionRoot: path.resolve(projectRoot),
|
|
1050
|
+
workRoot: path.resolve(resolveSessionRoot(input?.cwd)),
|
|
968
1051
|
});
|
|
969
1052
|
} catch (err) {
|
|
970
1053
|
// Registration failed — emit an observability breadcrumb to sweep.log.
|
|
@@ -973,6 +1056,9 @@ async function main() {
|
|
|
973
1056
|
}
|
|
974
1057
|
peers = await detectPeers({ sessionId }).catch(() => []);
|
|
975
1058
|
} catch { /* swallow — hook must remain non-blocking */ }
|
|
1059
|
+
// #1650 — foreign starts inside the window. detectPeers() already excludes
|
|
1060
|
+
// self, and its 15-min freshness covers the 5-min window (see helper JSDoc).
|
|
1061
|
+
const recentStarts = countRecentStarts(peers, { windowMin: START_WAVE_WINDOW_MIN });
|
|
976
1062
|
|
|
977
1063
|
// Append a peer line to the host banner when a banner was already emitted
|
|
978
1064
|
// and at least one peer is live on this host.
|
|
@@ -996,10 +1082,11 @@ async function main() {
|
|
|
996
1082
|
selfRepoHash && p.repo_path_hash ? p.repo_path_hash === selfRepoHash : false);
|
|
997
1083
|
const elsewhere = peers.filter((p) => !sameCopy.includes(p));
|
|
998
1084
|
|
|
999
|
-
const threshold = await peerWarnThreshold(projectRoot);
|
|
1000
1085
|
// WARN on any peer in THIS working copy — one is already enough to collide
|
|
1001
|
-
// — or on host-wide count
|
|
1002
|
-
|
|
1086
|
+
// — or on a start wave (#1650). The host-wide peer count stays on the line
|
|
1087
|
+
// as information only: as a WARN trigger it fired on 67.4% of starts.
|
|
1088
|
+
const startWave = recentStarts >= START_WAVE_MIN;
|
|
1089
|
+
const icon = sameCopy.length > 0 || startWave ? '⚠️ ' : '';
|
|
1003
1090
|
|
|
1004
1091
|
// Semantic ids are NOT unique — several live sessions routinely share e.g.
|
|
1005
1092
|
// `main-2026-08-21-session-2`, which makes a bare semantic label unusable
|
|
@@ -1011,6 +1098,9 @@ async function main() {
|
|
|
1011
1098
|
return `${label}${disc}:${p.branch ?? 'unknown'}:wave-${p.current_wave ?? 0}`;
|
|
1012
1099
|
};
|
|
1013
1100
|
pushBanner(`${icon}👥 Peers: ${peers.length} live on this host`);
|
|
1101
|
+
if (startWave) {
|
|
1102
|
+
pushBanner(` ⚠️ Start wave: ${recentStarts} sessions started or resumed in the last ${START_WAVE_WINDOW_MIN} min — stagger further starts`);
|
|
1103
|
+
}
|
|
1014
1104
|
if (sameCopy.length > 0) {
|
|
1015
1105
|
const list = sameCopy.slice(0, 3).map(fmt).join(', ');
|
|
1016
1106
|
const more = sameCopy.length > 3 ? ` +${sameCopy.length - 3} more` : '';
|
|
@@ -1040,7 +1130,8 @@ async function main() {
|
|
|
1040
1130
|
// Gated like the peer lines above: no host banner → no navigator line. A read
|
|
1041
1131
|
// failure must never block the hook — the helper is fail-closed itself, the
|
|
1042
1132
|
// try/catch only guards against the unexpected.
|
|
1043
|
-
|
|
1133
|
+
// #1559: solo users without fleet integration see no fleet line.
|
|
1134
|
+
if (bannerData && fleetIntegrationEnabled()) {
|
|
1044
1135
|
try {
|
|
1045
1136
|
pushBanner(navigatorBannerLine(await readNavigatorLease()));
|
|
1046
1137
|
} catch { /* swallow — hook must remain non-blocking */ }
|
|
@@ -1165,7 +1256,7 @@ async function main() {
|
|
|
1165
1256
|
pluginUpdate = await checkPluginUpdate({
|
|
1166
1257
|
cacheDir: path.join(projectRoot, '.orchestrator', 'runtime'),
|
|
1167
1258
|
// The probe's own fallback is env-only (SO_PLATFORM, then each harness's
|
|
1168
|
-
// compat var) and cannot run detectPlatform()'s
|
|
1259
|
+
// compat var) and cannot run detectPlatform()'s step 4, the marker-
|
|
1169
1260
|
// directory walk from cwd. This hook already resolved the platform at the
|
|
1170
1261
|
// top of run() through exactly that full precedence, so handing it over
|
|
1171
1262
|
// makes the remedy instruction MEASURED rather than guessed — a Codex or
|
|
@@ -1186,8 +1277,16 @@ async function main() {
|
|
|
1186
1277
|
// this root supersedes. `peer_count` is deliberately unchanged; this field
|
|
1187
1278
|
// makes the supersession rate measurable instead of inferred (HR-105).
|
|
1188
1279
|
peers_superseded: mechanicalPeersSuperseded,
|
|
1280
|
+
// #1650 — start-wave signal (HR-105): foreign registry entries whose
|
|
1281
|
+
// started_at lies within the last `recent_starts_window_min` minutes.
|
|
1282
|
+
recent_starts_count: recentStarts,
|
|
1283
|
+
recent_starts_window_min: START_WAVE_WINDOW_MIN,
|
|
1189
1284
|
};
|
|
1190
1285
|
if (headSha) payload.head_sha = headSha;
|
|
1286
|
+
if (upstreamSha !== null) {
|
|
1287
|
+
payload.upstream_sha = upstreamSha;
|
|
1288
|
+
payload.behind = behind;
|
|
1289
|
+
}
|
|
1191
1290
|
|
|
1192
1291
|
// #1091 — record the native SessionStart source so "does the same raw
|
|
1193
1292
|
// session_id repeat under source=resume?" becomes answerable from
|
package/hooks/on-stop.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import { AGENT_ID_RE, resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
|
|
|
47
47
|
|
|
48
48
|
import { emitEvent } from '../scripts/lib/events.mjs';
|
|
49
49
|
import { detectPlatform, getProjectDir, resolveSessionRoot } from '../scripts/lib/platform.mjs';
|
|
50
|
+
import { resolveOwnLockRoot } from './_lib/hook-root.mjs';
|
|
50
51
|
import { findOwnScopeFile } from '../scripts/lib/scope-gate.mjs';
|
|
51
52
|
import {
|
|
52
53
|
classifyManifestSession,
|
|
@@ -119,7 +120,7 @@ function discriminate(input) {
|
|
|
119
120
|
// dependency degradation (GH Kanevry/session-orchestrator#63)
|
|
120
121
|
// ---------------------------------------------------------------------------
|
|
121
122
|
//
|
|
122
|
-
// zx is loaded LAZILY. A static `import { $ } from 'zx'` fails at MODULE LOAD
|
|
123
|
+
// zx is loaded LAZILY. A static `import { $ } from '../scripts/lib/zx.mjs'` fails at MODULE LOAD
|
|
123
124
|
// time when node_modules is absent (interrupted install, EPERM sandbox, half-
|
|
124
125
|
// synced plugin cache), so the harness prints a 10-frame ERR_MODULE_NOT_FOUND
|
|
125
126
|
// stack on EVERY turn end with no hint that `npm install` is the fix. This
|
|
@@ -175,7 +176,7 @@ function warnDependenciesMissingOnce() {
|
|
|
175
176
|
*/
|
|
176
177
|
async function loadZx() {
|
|
177
178
|
try {
|
|
178
|
-
return (await import('zx')).$;
|
|
179
|
+
return (await import('../scripts/lib/zx.mjs')).$;
|
|
179
180
|
} catch (err) {
|
|
180
181
|
if (err?.code !== 'ERR_MODULE_NOT_FOUND') throw err;
|
|
181
182
|
warnDependenciesMissingOnce();
|
|
@@ -401,11 +402,13 @@ function reaperTriggerField(trigger) {
|
|
|
401
402
|
*/
|
|
402
403
|
async function handleStop(input, reaperTrigger = null) {
|
|
403
404
|
// Two roots, by writer (#1511 point b). `current-session.json` and
|
|
404
|
-
// `session.lock` (session id, lock heartbeat, duration)
|
|
405
|
-
//
|
|
405
|
+
// `session.lock` (session id, lock heartbeat, duration) are read where
|
|
406
|
+
// on-session-start.mjs wrote them: the launch dir, or — after EnterWorktree
|
|
407
|
+
// and a /clear, compact or resume there (#1605) — the entered worktree, found
|
|
408
|
+
// by the lock carrying this session's raw id (`resolveOwnLockRoot`). The wave
|
|
406
409
|
// and the commit/branch describe the tree the session WORKS in — after
|
|
407
410
|
// EnterWorktree that is the session root, not the launch checkout.
|
|
408
|
-
const projectRoot = getProjectDir();
|
|
411
|
+
const projectRoot = resolveOwnLockRoot(input, getProjectDir());
|
|
409
412
|
const sessionRoot = resolveSessionRoot(input?.cwd);
|
|
410
413
|
|
|
411
414
|
const wave = await readWaveNumber(sessionRoot, input);
|
|
@@ -453,7 +456,9 @@ async function handleStop(input, reaperTrigger = null) {
|
|
|
453
456
|
// is visible in sweep.log rather than invisible. One line per turn while
|
|
454
457
|
// the entry is absent; that volume IS the signal, and the next
|
|
455
458
|
// SessionStart's registerSelf() ends it.
|
|
456
|
-
|
|
459
|
+
// #1605 B4 — `work_root` follows the session into an entered worktree,
|
|
460
|
+
// so a relocating SessionStart of another session sees it working there.
|
|
461
|
+
const refreshed = await heartbeat(sessionId, { workRoot: path.resolve(sessionRoot) });
|
|
457
462
|
if (refreshed === null) {
|
|
458
463
|
logSweepEvent({
|
|
459
464
|
event: 'heartbeat-missing',
|
|
@@ -100,7 +100,12 @@ async function resolveSessionId(input, projectDir) {
|
|
|
100
100
|
|
|
101
101
|
let currentSession = null;
|
|
102
102
|
if (projectDir) {
|
|
103
|
-
|
|
103
|
+
// #1605 — read THIS session's record where on-session-start.mjs wrote it
|
|
104
|
+
// (the entered worktree after a /clear there), found by the lock carrying
|
|
105
|
+
// the raw id. Lazy: only a matched issue command pays for the lookup.
|
|
106
|
+
const { resolveOwnLockRoot } = await import('./_lib/hook-root.mjs');
|
|
107
|
+
const ownRoot = resolveOwnLockRoot({ session_id: nativeRawId, cwd: input?.cwd }, projectDir);
|
|
108
|
+
const persisted = path.join(ownRoot, '.orchestrator', 'current-session.json');
|
|
104
109
|
if (existsSync(persisted)) {
|
|
105
110
|
try {
|
|
106
111
|
currentSession = await readJson(persisted);
|
|
@@ -1093,9 +1093,10 @@ async function main() {
|
|
|
1093
1093
|
// manifest a Bash call just corrupted. `'own'` and `'unknown'` (legacy,
|
|
1094
1094
|
// unbound, or no resolvable own identity) fall through unchanged.
|
|
1095
1095
|
//
|
|
1096
|
-
// CEILING (BV-004): on a harness that supplies no session id at all (
|
|
1097
|
-
//
|
|
1098
|
-
//
|
|
1096
|
+
// CEILING (BV-004): on a harness that supplies no session id at all (Cursor
|
|
1097
|
+
// today) the own-id set is empty, so this gate is permanently `'unknown'` =
|
|
1098
|
+
// full pre-#1153 behaviour there. Codex CLI sends `session_id` in the hook
|
|
1099
|
+
// payload (measured 2026-10-20, codex-cli 0.160.1, #1691).
|
|
1099
1100
|
{
|
|
1100
1101
|
const { verdict, manifestIds } = classifyManifestSession(scope ?? {}, ownIds);
|
|
1101
1102
|
if (verdict === 'foreign') {
|
|
@@ -1172,7 +1173,10 @@ async function main() {
|
|
|
1172
1173
|
// `toContain('out-of-scope.mjs')` test to say otherwise.
|
|
1173
1174
|
const missingSnapshotNotice = prevScopeState === null
|
|
1174
1175
|
? formatSnapshotMissingNotice({
|
|
1175
|
-
|
|
1176
|
+
// #1634: the clock lives beside this session's own lock, which after
|
|
1177
|
+
// ExitWorktree is its `session_root`, not the manifest root. Loaded lazily:
|
|
1178
|
+
// this branch is rare and the module graph costs ~7 ms per Bash call.
|
|
1179
|
+
sessionAge: sessionAgeMs((await import('./_lib/hook-root.mjs')).resolveOwnLockRoot(input, context.manifestRoot)),
|
|
1176
1180
|
scopeAge: typeof scopeMtimeMs === 'number' ? Date.now() - scopeMtimeMs : null,
|
|
1177
1181
|
reason: snapshot === null ? 'absent' : 'untrusted',
|
|
1178
1182
|
})
|