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
|
@@ -80,6 +80,25 @@ import {
|
|
|
80
80
|
* ever observed writing outside its worktree despite this flag, the sandbox is
|
|
81
81
|
* not the boundary it is assumed to be here and the `hookTampering` check below
|
|
82
82
|
* becomes the only defence rather than the backstop.
|
|
83
|
+
*
|
|
84
|
+
* `--disable-codebase-ref` keeps the CLI from starting its codebase index
|
|
85
|
+
* (#1683). Without it every run leaves a `worker-server` and an
|
|
86
|
+
* `npx typescript-language-server --stdio` pair behind; they detach via setsid,
|
|
87
|
+
* so the timeout kill in `runChild` never reaches them. Measured 2026-10-19 on
|
|
88
|
+
* cursor-agent 2026.10.01-e373342: 3 orphans per run without the flag, 0 with
|
|
89
|
+
* it, same answer either way. The foreign agent loses semantic search and
|
|
90
|
+
* LSP-backed lints; read and grep stay, and the Claude diff review remains the
|
|
91
|
+
* quality anchor. Named ceiling (BV-004): the flag is registered in the CLI but
|
|
92
|
+
* missing from `--help`, so a release may drop it silently. Revisit trigger: a
|
|
93
|
+
* `worker-server` or `typescript-language-server` with PPID 1 after a dispatch.
|
|
94
|
+
*
|
|
95
|
+
* The hidden `--disable-indexing` flag is deliberately absent (#1687): it only
|
|
96
|
+
* gates the codebase sync inside that same `worker-server`, which this argv
|
|
97
|
+
* never starts. Measured 2026-10-20 on cursor-agent 2026.10.01-e373342 with
|
|
98
|
+
* per-process `nettop` against a 1.5 MB throwaway repo: 52–71 KB out per run
|
|
99
|
+
* with or without the flag, no worker-server, while a 1.5 MB loopback control
|
|
100
|
+
* read back as exactly 1,500,000 bytes. Revisit trigger: a dispatch whose
|
|
101
|
+
* outbound volume grows with the worktree size.
|
|
83
102
|
* @type {readonly string[]}
|
|
84
103
|
*/
|
|
85
104
|
export const FOREIGN_AGENT_BASE_ARGS = Object.freeze([
|
|
@@ -87,6 +106,7 @@ export const FOREIGN_AGENT_BASE_ARGS = Object.freeze([
|
|
|
87
106
|
'--force',
|
|
88
107
|
'--sandbox',
|
|
89
108
|
'enabled',
|
|
109
|
+
'--disable-codebase-ref',
|
|
90
110
|
]);
|
|
91
111
|
|
|
92
112
|
/**
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/** Windows tree cleanup: PowerShell 5.1 CIM identities, then taskkill and readback. */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
// Ceiling per native call: 5 s. Revisit if measured CIM startup exceeds this;
|
|
6
|
+
// a hung PowerShell must never hang gate cleanup (including the exit handler).
|
|
7
|
+
const OPTIONS = { encoding: 'utf8', shell: false, windowsHide: true, timeout: 5000, maxBuffer: 16 * 1024 * 1024 };
|
|
8
|
+
const system32 = () => path.join(process.env.SystemRoot || 'C:\\Windows', 'System32');
|
|
9
|
+
|
|
10
|
+
function readWindowsProcesses(spawnSyncFn = spawnSync) {
|
|
11
|
+
const result = spawnSyncFn(
|
|
12
|
+
path.join(system32(), 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
13
|
+
['-NoProfile', '-NonInteractive', '-Command', 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CreationDate | ConvertTo-Json -Compress'],
|
|
14
|
+
OPTIONS,
|
|
15
|
+
);
|
|
16
|
+
if (result.error || result.status !== 0) throw new Error('CIM snapshot failed');
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = JSON.parse(String(result.stdout));
|
|
20
|
+
} catch {
|
|
21
|
+
// A truncated or banner-polluted snapshot is "not measurable", never "no processes".
|
|
22
|
+
throw new Error('CIM snapshot output is not JSON');
|
|
23
|
+
}
|
|
24
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
25
|
+
if (!rows.length || rows.some(p => !p || !Number.isInteger(p.ProcessId)
|
|
26
|
+
|| !Number.isInteger(p.ParentProcessId))) {
|
|
27
|
+
throw new Error('CIM snapshot has no usable process identities');
|
|
28
|
+
}
|
|
29
|
+
return rows;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// PowerShell 5.1 ConvertTo-Json uses /Date(epoch milliseconds)/.
|
|
33
|
+
// Other formats (including WMI dates) remain explicitly unmeasurable.
|
|
34
|
+
function creationMs(row) {
|
|
35
|
+
const match = /^\/Date\((-?\d+)\)\/$/.exec(row?.CreationDate);
|
|
36
|
+
return match && Number.isFinite(Number(match[1])) ? Number(match[1]) : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function descendantIds(rows, seeds) {
|
|
40
|
+
const ids = new Set(seeds);
|
|
41
|
+
let size;
|
|
42
|
+
do {
|
|
43
|
+
size = ids.size;
|
|
44
|
+
for (const p of rows) if (ids.has(p.ParentProcessId)) ids.add(p.ProcessId);
|
|
45
|
+
} while (ids.size !== size);
|
|
46
|
+
return ids;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Same synchronous primitive for timeout and process-exit cleanup.
|
|
50
|
+
* A live ChildProcess holds libuv's Windows handle, preventing root PID reuse.
|
|
51
|
+
* After exit, only dated descendants may be targeted; the root is never killed.
|
|
52
|
+
* null survivors means unmeasurable, never a successful empty measurement.
|
|
53
|
+
*/
|
|
54
|
+
export function killWindowsProcessTree(pid, {
|
|
55
|
+
spawnSyncFn = spawnSync,
|
|
56
|
+
snapshotFn = () => readWindowsProcesses(spawnSyncFn),
|
|
57
|
+
child,
|
|
58
|
+
spawnedAtMs,
|
|
59
|
+
verifyTree = true,
|
|
60
|
+
} = {}) {
|
|
61
|
+
const base = { signalsSent: [], aborted: null };
|
|
62
|
+
const unknown = reason => ({ ...base, ok: false, survivors: null, error: `nicht messbar auf win32: ${reason}` });
|
|
63
|
+
const rootHeld = child?.exitCode === null && child?.signalCode === null;
|
|
64
|
+
let killError = null;
|
|
65
|
+
const kill = (target, tree = true) => {
|
|
66
|
+
base.signalsSent.push('taskkill');
|
|
67
|
+
try {
|
|
68
|
+
const args = tree ? ['/PID', String(target), '/T', '/F'] : ['/PID', String(target), '/F'];
|
|
69
|
+
const result = spawnSyncFn(path.join(system32(), 'taskkill.exe'), args, OPTIONS);
|
|
70
|
+
if (result.error || result.status !== 0) killError = result.error?.message ?? `taskkill exit ${result.status}`;
|
|
71
|
+
} catch (err) { killError = String(err); }
|
|
72
|
+
};
|
|
73
|
+
if (!verifyTree) {
|
|
74
|
+
if (rootHeld) kill(pid);
|
|
75
|
+
return unknown(`nicht gemessen (verifyTree: false)${killError ? `; ${killError}` : ''}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let before;
|
|
79
|
+
let measurementError;
|
|
80
|
+
try {
|
|
81
|
+
before = snapshotFn();
|
|
82
|
+
if (!Array.isArray(before)) throw Error('invalid snapshot');
|
|
83
|
+
} catch (err) { measurementError = err; }
|
|
84
|
+
if (measurementError) {
|
|
85
|
+
if (rootHeld) kill(pid);
|
|
86
|
+
return unknown(measurementError);
|
|
87
|
+
}
|
|
88
|
+
const ids = descendantIds(before, [pid]);
|
|
89
|
+
const floor = Number.isFinite(spawnedAtMs) ? spawnedAtMs - 2000 : null;
|
|
90
|
+
let targets = before.filter(p => ids.has(p.ProcessId));
|
|
91
|
+
if (!rootHeld) {
|
|
92
|
+
targets = targets.filter(p => p.ProcessId !== pid);
|
|
93
|
+
if (floor === null || targets.some(p => creationMs(p) === null)) return unknown('missing descendant CreationDate or spawnedAtMs');
|
|
94
|
+
targets = targets.filter(p => creationMs(p) >= floor);
|
|
95
|
+
for (const p of targets) kill(p.ProcessId);
|
|
96
|
+
} else {
|
|
97
|
+
// Windows never rewrites ParentProcessId when a parent dies, so a process
|
|
98
|
+
// older than our root can still name the root's (reused) PID as its parent
|
|
99
|
+
// (Opus review M2, #1612). Such a process is not ours: drop it from the
|
|
100
|
+
// targets, and when one exists, end ours one by one without /T so
|
|
101
|
+
// taskkill's own PPID walk cannot reach it.
|
|
102
|
+
const rootBorn = creationMs(targets.find(p => p.ProcessId === pid)) ?? floor;
|
|
103
|
+
const stale = rootBorn === null ? [] : targets.filter(p => p.ProcessId !== pid
|
|
104
|
+
&& creationMs(p) !== null && creationMs(p) < rootBorn);
|
|
105
|
+
if (stale.length === 0) {
|
|
106
|
+
kill(pid);
|
|
107
|
+
} else {
|
|
108
|
+
targets = targets.filter(p => !stale.includes(p));
|
|
109
|
+
for (const p of targets) kill(p.ProcessId, false);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let after;
|
|
114
|
+
try {
|
|
115
|
+
after = snapshotFn();
|
|
116
|
+
if (!Array.isArray(after)) throw Error('invalid snapshot');
|
|
117
|
+
} catch (err) { return unknown(err); }
|
|
118
|
+
if ((rootHeld && !targets.some(p => p.ProcessId === pid))
|
|
119
|
+
|| targets.some(p => creationMs(p) === null)
|
|
120
|
+
|| after.some(p => targets.some(q => q.ProcessId === p.ProcessId) && creationMs(p) === null)) {
|
|
121
|
+
return unknown('missing tree identity');
|
|
122
|
+
}
|
|
123
|
+
const survivors = new Set(targets.filter(p => after.some(q =>
|
|
124
|
+
q.ProcessId === p.ProcessId && q.CreationDate === p.CreationDate)).map(p => p.ProcessId));
|
|
125
|
+
|
|
126
|
+
// Include descendants born during taskkill, following parents through both
|
|
127
|
+
// snapshots even when an intermediate parent has already disappeared.
|
|
128
|
+
const treeIds = new Set([pid, ...targets.map(p => p.ProcessId)]);
|
|
129
|
+
const related = descendantIds([...before, ...after], treeIds);
|
|
130
|
+
for (const p of after) {
|
|
131
|
+
if (p.ProcessId === pid || !related.has(p.ParentProcessId)) continue;
|
|
132
|
+
const date = creationMs(p);
|
|
133
|
+
if (date === null || floor === null) return unknown('missing descendant CreationDate or spawnedAtMs');
|
|
134
|
+
if (date >= floor) survivors.add(p.ProcessId);
|
|
135
|
+
}
|
|
136
|
+
return { ...base, ok: survivors.size === 0 && !killError, survivors: [...survivors], error: killError };
|
|
137
|
+
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import fs from 'node:fs/promises';
|
|
14
14
|
import fsSync from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
|
-
import { $ } from 'zx';
|
|
16
|
+
import { $ } from './zx.mjs';
|
|
17
17
|
import { listWorktrees } from './worktree.mjs';
|
|
18
18
|
|
|
19
19
|
// Do not spam stdout/stderr with git command echoes.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* lifecycle → config.mjs (readConfigFile, parseSessionConfig)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { $, nothrow, ProcessOutput } from 'zx';
|
|
20
|
+
import { $, nothrow, ProcessOutput } from '../zx.mjs';
|
|
21
21
|
import fs from 'node:fs/promises';
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
import path from 'node:path';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync as fileExistsSync } from 'node:fs';
|
|
2
|
+
import { win32 as path } from 'node:path';
|
|
3
|
+
import { $, quote } from 'zx';
|
|
4
|
+
|
|
5
|
+
export * from 'zx';
|
|
6
|
+
|
|
7
|
+
// Windows 11 / Node 24.14 / zx 8.8.5 / Git 2.55, measured 2026-10-14:
|
|
8
|
+
// MSYS halves backslashes in Node's -c command line before bash interprets
|
|
9
|
+
// ANSI-C quoting (\b becomes backspace, \r CR). Doubling zx's escapes gave
|
|
10
|
+
// 14/14 exact argument roundtrips, versus 10/14 with stock quote (#1612).
|
|
11
|
+
// Always start from stock quote, so repeated configuration cannot stack escapes.
|
|
12
|
+
const quoteGitBash = (arg) => quote(arg).replace(/\\/g, '\\\\');
|
|
13
|
+
|
|
14
|
+
/** Configure the zx singleton; injection also permits Windows checks on POSIX. */
|
|
15
|
+
export function configureZx({
|
|
16
|
+
platform = process.platform,
|
|
17
|
+
existsSync = fileExistsSync,
|
|
18
|
+
target = $,
|
|
19
|
+
env = process.env,
|
|
20
|
+
} = {}) {
|
|
21
|
+
if (platform !== 'win32') return;
|
|
22
|
+
|
|
23
|
+
const shell = typeof target.shell === 'string' ? path.normalize(target.shell) : '';
|
|
24
|
+
const wsl = path.join(env.SystemRoot || 'C:\\Windows', 'System32', 'bash.exe');
|
|
25
|
+
if (!target.shell || shell.toLowerCase() === wsl.toLowerCase()) {
|
|
26
|
+
const candidates = [env.ProgramFiles, env['ProgramFiles(x86)']]
|
|
27
|
+
.filter(Boolean).map(dir => path.join(dir, 'Git', 'bin', 'bash.exe'));
|
|
28
|
+
let bash = candidates.find(candidate => existsSync(candidate));
|
|
29
|
+
if (!bash) {
|
|
30
|
+
for (const dir of (env.PATH || '').split(';').filter(Boolean)) {
|
|
31
|
+
const gitDir = dir.replace(/^"|"$/g, '');
|
|
32
|
+
if (!existsSync(path.join(gitDir, 'git.exe'))) continue;
|
|
33
|
+
const candidate = path.resolve(gitDir, '..', 'bin', 'bash.exe');
|
|
34
|
+
if (existsSync(candidate)) {
|
|
35
|
+
bash = candidate;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!bash) return;
|
|
41
|
+
target.shell = bash;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof target.shell === 'string' &&
|
|
45
|
+
/\\(?:Git\\bin|usr\\bin)\\bash\.exe$/i.test(path.normalize(target.shell))) {
|
|
46
|
+
target.quote = quoteGitBash;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
configureZx();
|
package/scripts/mcp-server.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { join } from 'node:path';
|
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
import { isSessionConfigHeading } from './lib/config/section-extractor.mjs';
|
|
8
8
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
9
|
+
import { canonicalizeSessions, isMeasuredSession } from './lib/sessions-canonical.mjs';
|
|
10
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
9
11
|
|
|
10
12
|
const SERVER_INFO = { name: 'session-orchestrator', version: '2.0.0' };
|
|
11
13
|
const TOOLS = [
|
|
@@ -16,7 +18,7 @@ const TOOLS = [
|
|
|
16
18
|
},
|
|
17
19
|
{
|
|
18
20
|
name: 'session_metrics',
|
|
19
|
-
description: 'Reads the last 5
|
|
21
|
+
description: 'Reads the last 5 MEASURED session metrics entries from .orchestrator/metrics/sessions.jsonl (unmeasured records excluded)',
|
|
20
22
|
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
21
23
|
},
|
|
22
24
|
];
|
|
@@ -65,24 +67,31 @@ export function readSessionConfig(root) {
|
|
|
65
67
|
return body.join('\n').replace(/\n+$/, '') || `No '## Session Config' section found in ${file}`;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
/** Read the last five
|
|
70
|
+
/** Read the last five canonical measured records, with exclusions and token coverage. */
|
|
69
71
|
export function readSessionMetrics(root) {
|
|
70
|
-
const file = ['.orchestrator', '.claude'].map((dir) => join(root, dir, 'metrics', 'sessions.jsonl'))
|
|
72
|
+
const file = ['.orchestrator', '.claude'].map((dir) => join(resolveEventsLedgerRoot(root), dir, 'metrics', 'sessions.jsonl'))
|
|
71
73
|
.find((path) => isFile(path));
|
|
72
74
|
if (!file) return 'No metrics found (checked .orchestrator/metrics/ and .claude/metrics/)';
|
|
73
75
|
const entries = [];
|
|
74
76
|
let stubCount = 0;
|
|
77
|
+
const parsed = [];
|
|
75
78
|
for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
|
|
76
79
|
let record;
|
|
77
80
|
try { record = JSON.parse(line); } catch { continue; }
|
|
78
|
-
//
|
|
81
|
+
// Preserve torn-write/non-record guards and actionable read errors.
|
|
79
82
|
if (!record || typeof record !== 'object' || Array.isArray(record)) continue;
|
|
80
|
-
|
|
83
|
+
parsed.push(record);
|
|
84
|
+
}
|
|
85
|
+
// Keep ID-less legacy measurements and original append order for the window.
|
|
86
|
+
const canonical = new Set(canonicalizeSessions(parsed, { keepUnidentified: true }));
|
|
87
|
+
for (const record of parsed) {
|
|
88
|
+
if (!canonical.has(record)) continue;
|
|
89
|
+
if (!isMeasuredSession(record)) { stubCount++; continue; }
|
|
81
90
|
entries.push(record);
|
|
82
91
|
if (entries.length > 5) entries.shift();
|
|
83
92
|
}
|
|
84
93
|
if (!entries.length) {
|
|
85
|
-
return stubCount ? `No
|
|
94
|
+
return stubCount ? `No measured sessions (unmeasured records excluded: ${stubCount})`
|
|
86
95
|
: 'No metrics found (file is empty)';
|
|
87
96
|
}
|
|
88
97
|
const record = entries.findLast((entry) => (entry.total_token_input !== null && entry.total_token_input !== undefined)
|
|
@@ -95,7 +104,7 @@ export function readSessionMetrics(root) {
|
|
|
95
104
|
+ ` [session: ${record.session_id ?? 'unknown'}]`
|
|
96
105
|
: 'tokens: no token data in last 5 sessions (subagent telemetry not yet captured)';
|
|
97
106
|
return entries.map((entry) => JSON.stringify(entry)).join('\n')
|
|
98
|
-
+ `\n\
|
|
107
|
+
+ `\n\nunmeasured records excluded: ${stubCount}\n\n--- token summary ---\n${summary}`;
|
|
99
108
|
}
|
|
100
109
|
|
|
101
110
|
/** Handle one JSON-RPC line. null means a notification or blank line, not a reply. */
|
package/scripts/mcp-server.sh
CHANGED
|
@@ -8,8 +8,8 @@ set -euo pipefail
|
|
|
8
8
|
# - session_config — reads Session Config from the project instruction file
|
|
9
9
|
# (CLAUDE.md, or AGENTS.md alias on Codex CLI — see
|
|
10
10
|
# skills/_shared/instruction-file-resolution.md)
|
|
11
|
-
# - session_metrics — reads last 5
|
|
12
|
-
#
|
|
11
|
+
# - session_metrics — reads last 5 canonical MEASURED session metrics entries
|
|
12
|
+
# via the shared native reader (#1584)
|
|
13
13
|
|
|
14
14
|
# ---------------------------------------------------------------------------
|
|
15
15
|
# Helpers
|
|
@@ -85,7 +85,7 @@ handle_tools_list() {
|
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"name": "session_metrics",
|
|
88
|
-
"description": "Reads the last 5
|
|
88
|
+
"description": "Reads the last 5 MEASURED session metrics entries from .orchestrator/metrics/sessions.jsonl (unmeasured records excluded)",
|
|
89
89
|
"inputSchema": {"type": "object", "properties": {}, "required": []}
|
|
90
90
|
}
|
|
91
91
|
]
|
|
@@ -171,88 +171,18 @@ tool_session_metrics() {
|
|
|
171
171
|
return
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# — 0 waves, seconds of runtime) BEFORE taking the tail, so a recent phantom
|
|
185
|
-
# cannot displace real session records out of the last-5 window.
|
|
186
|
-
#
|
|
187
|
-
# `-R` (raw-input) + `fromjson?` parses each line individually and SKIPS
|
|
188
|
-
# unparseable ones instead of aborting the whole stream — plain
|
|
189
|
-
# `jq -c 'select(...)'` aborts at the FIRST malformed line (jq: parse error,
|
|
190
|
-
# exit 5), which is fatal here because sessions.jsonl is append-only from
|
|
191
|
-
# multiple writers and a torn write is exactly the case that matters. This
|
|
192
|
-
# mirrors the per-line try/catch behaviour of the .mjs path
|
|
193
|
-
# (scripts/lib/session-schema/filters.mjs).
|
|
194
|
-
local entries
|
|
195
|
-
entries=$(jq -R -c 'fromjson? | select(.status != "abandoned")' "$metrics_file" 2>/dev/null | tail -n 5) || true
|
|
196
|
-
|
|
197
|
-
# Report the stubs the filter above dropped (#1296) — counted over the WHOLE
|
|
198
|
-
# file, not the tail, with the same torn-line tolerance (`fromjson?`, plus
|
|
199
|
-
# `.status?` so a non-object line cannot abort the count either). Computed
|
|
200
|
-
# BEFORE the empty check: a stubs-only ledger is not an empty file.
|
|
201
|
-
local stub_count
|
|
202
|
-
stub_count=$(jq -R -n '[inputs | fromjson? | select(.status? == "abandoned")] | length' "$metrics_file" 2>/dev/null) || stub_count=0
|
|
203
|
-
[[ -z "$stub_count" ]] && stub_count=0
|
|
204
|
-
|
|
205
|
-
if [[ -z "$entries" ]]; then
|
|
206
|
-
if [[ "$stub_count" -gt 0 ]]; then
|
|
207
|
-
respond "$id" "$(text_content "No real sessions (abandoned stubs excluded: ${stub_count})")"
|
|
208
|
-
else
|
|
209
|
-
respond "$id" "$(text_content "No metrics found (file is empty)")"
|
|
210
|
-
fi
|
|
174
|
+
# Delegate to the same reader as the native transport: canonical identity,
|
|
175
|
+
# measured-session definition, main-checkout ledger and token summary.
|
|
176
|
+
local server_dir output
|
|
177
|
+
server_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
178
|
+
if ! output=$(node --input-type=module -e '
|
|
179
|
+
import { pathToFileURL } from "node:url";
|
|
180
|
+
const { readSessionMetrics } = await import(pathToFileURL(process.argv[2]).href);
|
|
181
|
+
process.stdout.write(readSessionMetrics(process.argv[3]));
|
|
182
|
+
' session-metrics-reader "$server_dir/mcp-server.mjs" "$project_root" </dev/null); then
|
|
183
|
+
respond_error "$id" -32603 "Unable to read session data"
|
|
211
184
|
return
|
|
212
185
|
fi
|
|
213
|
-
|
|
214
|
-
# Derive a token summary from the most-recent record that carries token totals.
|
|
215
|
-
# Fields total_token_input / total_token_output / subagents_with_tokens are
|
|
216
|
-
# optional (null when no subagent token data was captured for that session).
|
|
217
|
-
# Low coverage (subagents_with_tokens == 0 while matched_records > 0) means
|
|
218
|
-
# the total is PARTIAL — do not read it as "free / zero cost".
|
|
219
|
-
local token_summary
|
|
220
|
-
# Use -Rrs (raw-input + slurp + raw-output) so jq receives the multi-line
|
|
221
|
-
# JSONL block as a single string, can split + parse each line independently,
|
|
222
|
-
# and outputs plain text (no surrounding JSON quotes on the result string).
|
|
223
|
-
# The plain -r flag alone does not work for multi-record JSONL piped as one
|
|
224
|
-
# string — it treats the entire input as a single JSON value.
|
|
225
|
-
token_summary=$(printf '%s' "$entries" | \
|
|
226
|
-
jq -Rrs '
|
|
227
|
-
# Parse each non-empty line as JSON; skip malformed lines.
|
|
228
|
-
[ split("\n")[] | select(length > 0) | (try fromjson catch null) | select(. != null) ]
|
|
229
|
-
| reverse
|
|
230
|
-
| ( map(select( (.total_token_input != null) or (.total_token_output != null) )) | first ) as $rec
|
|
231
|
-
| if $rec then
|
|
232
|
-
"tokens: \($rec.total_token_input // "?") in / \($rec.total_token_output // "?") out" +
|
|
233
|
-
" (coverage: \($rec.subagents_with_tokens // 0) subagents)" +
|
|
234
|
-
( if ($rec.subagents_with_tokens // 0) == 0 then
|
|
235
|
-
" ⚠ partial — subagent token data missing; total is not a reliable cost estimate"
|
|
236
|
-
else "" end ) +
|
|
237
|
-
" [session: \($rec.session_id // "unknown")]"
|
|
238
|
-
else
|
|
239
|
-
"tokens: no token data in last 5 sessions (subagent telemetry not yet captured)"
|
|
240
|
-
end
|
|
241
|
-
' 2>/dev/null) || token_summary=""
|
|
242
|
-
|
|
243
|
-
local output
|
|
244
|
-
entries="${entries}
|
|
245
|
-
|
|
246
|
-
abandoned stubs excluded: ${stub_count}"
|
|
247
|
-
if [[ -n "$token_summary" ]]; then
|
|
248
|
-
output="${entries}
|
|
249
|
-
|
|
250
|
-
--- token summary ---
|
|
251
|
-
${token_summary}"
|
|
252
|
-
else
|
|
253
|
-
output="$entries"
|
|
254
|
-
fi
|
|
255
|
-
|
|
256
186
|
respond "$id" "$(text_content "$output")"
|
|
257
187
|
}
|
|
258
188
|
|
|
@@ -37,7 +37,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
37
37
|
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
38
38
|
import { join, resolve } from 'node:path';
|
|
39
39
|
import { tmpdir } from 'node:os';
|
|
40
|
-
import { fileURLToPath } from 'node:url';
|
|
40
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
41
41
|
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
// CLI flags
|
|
@@ -57,8 +57,8 @@ const _REPO_ROOT = resolve(flagValue('--repo-root', process.cwd()));
|
|
|
57
57
|
const JSON_MODE = flagBool('--json');
|
|
58
58
|
|
|
59
59
|
const SCRIPT_DIR = fileURLToPath(new URL('.', import.meta.url));
|
|
60
|
-
const CACHE_MODULE = join(SCRIPT_DIR, 'lib', 'quality-gates-cache.mjs');
|
|
61
|
-
const POLICY_MODULE = join(SCRIPT_DIR, 'lib', 'quality-gates-policy.mjs');
|
|
60
|
+
const CACHE_MODULE = pathToFileURL(join(SCRIPT_DIR, 'lib', 'quality-gates-cache.mjs')).href;
|
|
61
|
+
const POLICY_MODULE = pathToFileURL(join(SCRIPT_DIR, 'lib', 'quality-gates-policy.mjs')).href;
|
|
62
62
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
// Helpers
|
|
@@ -340,11 +340,13 @@ async function main() {
|
|
|
340
340
|
} else {
|
|
341
341
|
printHumanReport(report);
|
|
342
342
|
}
|
|
343
|
-
|
|
344
|
-
process.exit(0);
|
|
345
343
|
} finally {
|
|
346
344
|
teardownTmpRepo(tmpRepo);
|
|
347
345
|
}
|
|
346
|
+
// Exit only AFTER the finally: process.exit() ends the process on the spot,
|
|
347
|
+
// so calling it inside the try skipped teardownTmpRepo and leaked one
|
|
348
|
+
// pce-repo-* dir under os.tmpdir() on every successful run (#1648).
|
|
349
|
+
process.exit(0);
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
function deriveRecommendation(cacheSubprocess, cacheInProcess, policySubprocess, _policyInProcess) {
|
|
@@ -278,12 +278,19 @@ let stateMdReadOk = false;
|
|
|
278
278
|
let stateDir = null;
|
|
279
279
|
|
|
280
280
|
try {
|
|
281
|
-
// resolveStateMdPath from state-md.mjs: falls back to .claude/STATE.md
|
|
282
281
|
const stateMdMod = await import('./lib/state-md.mjs');
|
|
283
|
-
const
|
|
282
|
+
const { resolveStateMdWriteTarget, warnForeignStateMd } = await import(
|
|
283
|
+
'./lib/state-md/frontmatter-mutators.mjs'
|
|
284
|
+
);
|
|
285
|
+
// #1636: the write side's ownership rule — another platform's STATE.md not
|
|
286
|
+
// proven to be this process's takes the "missing" branch. On rejection
|
|
287
|
+
// `stateMdPath` is this platform's own target, so stateDir (also the wave
|
|
288
|
+
// manifest's dir) never points into the foreign platform's state dir.
|
|
289
|
+
const { path: stateMdPath, rejected } = resolveStateMdWriteTarget(process.cwd());
|
|
290
|
+
if (rejected) warnForeignStateMd('memory-propose', rejected, stateMdPath);
|
|
284
291
|
stateDir = dirname(stateMdPath);
|
|
285
292
|
|
|
286
|
-
if (!existsSync(stateMdPath)) {
|
|
293
|
+
if (rejected || !existsSync(stateMdPath)) {
|
|
287
294
|
if (!dryRun) {
|
|
288
295
|
exit(
|
|
289
296
|
{ status: STATUS.REJECTED_WRONG_CONTEXT, detail: 'STATE.md missing or unparseable' },
|
|
@@ -245,6 +245,13 @@ async function processRepo(repo, { apply }) {
|
|
|
245
245
|
|
|
246
246
|
// Sessions.jsonl liveness check — even if marker is absent, an active repo
|
|
247
247
|
// should not get a "welcome" banner.
|
|
248
|
+
// Deliberately THIS path's own ledger, not resolveEventsLedgerRoot(repo)
|
|
249
|
+
// (#1592): the inputs are main-checkout repo paths by construction
|
|
250
|
+
// (--repos / dormant-repos), and the marker above is seeded into this same
|
|
251
|
+
// path — liveness and seed stay one checkout. Ceiling: a linked worktree
|
|
252
|
+
// passed via --repos is judged by its own (absent) ledger; revisit if
|
|
253
|
+
// worktree paths ever enter dormant-repos.
|
|
254
|
+
// ledger-root: deliberate own-path ledger (#1592), see the comment above
|
|
248
255
|
const sessionsPath = join(repo, SESSIONS_REL);
|
|
249
256
|
try {
|
|
250
257
|
if (await sessionsHasContent(sessionsPath)) {
|
|
@@ -7,6 +7,28 @@ set -euo pipefail
|
|
|
7
7
|
ROOT="${1:-$(pwd)}"
|
|
8
8
|
CANONICAL="$ROOT/.orchestrator/metrics/learnings.jsonl"
|
|
9
9
|
TODAY="$(date +%Y-%m-%d)"
|
|
10
|
+
|
|
11
|
+
# #1516: a host whose learnings-store role is `inbox` never rewrites the
|
|
12
|
+
# canonical store. The role resolver is the JS one (env SO_LEARNINGS_STORE_ROLE
|
|
13
|
+
# > owner.yaml learnings.store-role > writer), imported relative to this script.
|
|
14
|
+
# Fail-closed: if node fails, or the role is `invalid` (set but unusable) or
|
|
15
|
+
# anything other than writer/inbox, nothing is written and the script exits 4.
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
17
|
+
STORE_ROLE="$(node --input-type=module -e '
|
|
18
|
+
const { pathToFileURL } = await import("node:url");
|
|
19
|
+
const m = await import(pathToFileURL(process.argv[1]).href);
|
|
20
|
+
process.stdout.write(m.resolveStoreRole().role);
|
|
21
|
+
' "$SCRIPT_DIR/lib/learnings/store-role.mjs" 2>/dev/null)" || STORE_ROLE="unresolved"
|
|
22
|
+
if [ "$STORE_ROLE" = "inbox" ]; then
|
|
23
|
+
echo "migrate-legacy-learnings: learnings store role is inbox on this host (#1516) — nothing written; run it on the writer host" >&2
|
|
24
|
+
printf '{"status":"refused_store_role"}\n'
|
|
25
|
+
exit 3
|
|
26
|
+
fi
|
|
27
|
+
if [ "$STORE_ROLE" != "writer" ]; then
|
|
28
|
+
echo "migrate-legacy-learnings: learnings store role is ${STORE_ROLE} on this host (#1516) — nothing written; fix owner.yaml learnings.store-role or SO_LEARNINGS_STORE_ROLE" >&2
|
|
29
|
+
printf '{"status":"refused_store_role_invalid"}\n'
|
|
30
|
+
exit 4
|
|
31
|
+
fi
|
|
10
32
|
LEGACY_PATHS=("$ROOT/.claude/metrics/learnings.jsonl" "$ROOT/.codex/metrics/learnings.jsonl" "$ROOT/.cursor/metrics/learnings.jsonl")
|
|
11
33
|
|
|
12
34
|
tmp=""
|
|
@@ -39,9 +39,10 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import { readFileSync, copyFileSync, existsSync } from 'node:fs';
|
|
42
|
-
import { basename } from 'node:path';
|
|
42
|
+
import { basename, join, resolve } from 'node:path';
|
|
43
43
|
import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
|
|
44
44
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
45
|
+
import { resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
45
46
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
46
47
|
|
|
47
48
|
const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
|
|
@@ -327,8 +328,21 @@ function processEntry(raw) {
|
|
|
327
328
|
// CLI
|
|
328
329
|
// ---------------------------------------------------------------------------
|
|
329
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Default target: sessions.jsonl of the repo's one ledger root (#1514/#1584,
|
|
333
|
+
* #1722) — the main checkout when cwd is a linked worktree. Outside one the
|
|
334
|
+
* relative DEFAULT_FILE is kept verbatim, so paths in output do not change.
|
|
335
|
+
*
|
|
336
|
+
* @returns {string}
|
|
337
|
+
*/
|
|
338
|
+
function defaultFile() {
|
|
339
|
+
const root = resolveEventsLedgerRoot(process.cwd());
|
|
340
|
+
// ledger-root: `root` is resolveEventsLedgerRoot() of the line above
|
|
341
|
+
return root === resolve(process.cwd()) ? DEFAULT_FILE : join(root, DEFAULT_FILE);
|
|
342
|
+
}
|
|
343
|
+
|
|
330
344
|
function parseArgs(argv) {
|
|
331
|
-
const args = { file:
|
|
345
|
+
const args = { file: defaultFile(), apply: false };
|
|
332
346
|
for (let i = 0; i < argv.length; i++) {
|
|
333
347
|
const a = argv[i];
|
|
334
348
|
if (a === '--file') {
|