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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-work-trees.mjs — which trees did a session commit in, and from which
|
|
3
|
+
* start ref (#1659).
|
|
4
|
+
*
|
|
5
|
+
* `session-start-ref` names ONE sha in ONE tree. A coordinator that commits in
|
|
6
|
+
* several of its own worktrees (`git -C <wt> commit`, `cd <wt> && git commit`,
|
|
7
|
+
* subagents committing there) leaves those commits unattributed, and a ref from
|
|
8
|
+
* a different tree is no fix: a two-dot diff across two trees lists foreign
|
|
9
|
+
* files. hooks/pre-bash-staging-fence.mjs therefore records, before every
|
|
10
|
+
* `git commit`, the pair `(tree, start_ref)` as an
|
|
11
|
+
* `orchestrator.session.work_tree` event in the events ledger. This module is
|
|
12
|
+
* the reader:
|
|
13
|
+
*
|
|
14
|
+
* - {@link readSessionWorkTrees} — the session's trees, FIRST record per tree
|
|
15
|
+
* wins (the writer does not dedupe; every commit appends one record).
|
|
16
|
+
* - {@link sessionCommitFiles} — per tree `git diff --name-only <ref>..HEAD`
|
|
17
|
+
* INSIDE THAT TREE ONLY. A diff between two different trees is never
|
|
18
|
+
* computed.
|
|
19
|
+
*
|
|
20
|
+
* Neither function throws. Unreadable ledger lines are COUNTED, not silently
|
|
21
|
+
* skipped (`.claude/rules/measurement-discipline.md` § Unlesbare JSONL-Zeilen
|
|
22
|
+
* zaehlen); an unusable tree is reported `skipped` with a reason.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
|
|
29
|
+
import { readEventsWithRotations } from './events.mjs';
|
|
30
|
+
import { SESSION_WORK_TREE_EVENT } from './events-schema.mjs';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Events-ledger window for the #1401 census (`events-retention-banner.mjs`):
|
|
34
|
+
* `null` — {@link readSessionWorkTrees} reads back to a session's FIRST
|
|
35
|
+
* `orchestrator.session.work_tree` record, however old, so no day count bounds
|
|
36
|
+
* it. A cut history loses that tree's first start_ref; the reader then takes
|
|
37
|
+
* the oldest surviving record for it, which under-reports that tree (#1659).
|
|
38
|
+
*/
|
|
39
|
+
export const REQUIRED_EVENTS_WINDOW_DAYS = null;
|
|
40
|
+
|
|
41
|
+
/** Per-git-call timeout. A hung git must not hang a consumer (close, gate). */
|
|
42
|
+
const GIT_TIMEOUT_MS = 10_000;
|
|
43
|
+
|
|
44
|
+
/** Full sha, SHA-1 or SHA-256 object format. */
|
|
45
|
+
const FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read the `(tree, start_ref)` pairs one session recorded.
|
|
49
|
+
*
|
|
50
|
+
* Only records whose `session_id` EQUALS `sessionId` (the raw harness id — a
|
|
51
|
+
* dispatched subagent carries the coordinator's raw id, so its commits count
|
|
52
|
+
* for the coordinator, as intended). The first record per `tree` wins, in
|
|
53
|
+
* ledger order (rotated archives included, oldest first). A record of the
|
|
54
|
+
* right event and session but with a missing/invalid `tree` or `start_ref` is
|
|
55
|
+
* counted in `invalid`, never used.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} args
|
|
58
|
+
* @param {string} args.sessionId — raw session id to match.
|
|
59
|
+
* @param {string} [args.eventsFile] — explicit active ledger path (wins over repoRoot).
|
|
60
|
+
* @param {string} [args.repoRoot] — project root whose ledger is read; defaults
|
|
61
|
+
* like `eventsFilePath()` (`resolveEventsLedgerRoot()`).
|
|
62
|
+
* @returns {{ trees: Array<{ tree: string, start_ref: string }>,
|
|
63
|
+
* malformed: number, invalid: number, complete: boolean|null,
|
|
64
|
+
* error?: string }}
|
|
65
|
+
* `malformed` = unreadable ledger lines (all sessions, the reader cannot
|
|
66
|
+
* attribute a line it cannot parse); `complete` mirrors
|
|
67
|
+
* `readEventsWithRotations` (`null` = no ledger found, NOT "no trees").
|
|
68
|
+
*/
|
|
69
|
+
export function readSessionWorkTrees({ sessionId, eventsFile, repoRoot } = {}) {
|
|
70
|
+
if (typeof sessionId !== 'string' || sessionId.trim() === '') {
|
|
71
|
+
return { trees: [], malformed: 0, invalid: 0, complete: null, error: 'sessionId missing' };
|
|
72
|
+
}
|
|
73
|
+
let read;
|
|
74
|
+
try {
|
|
75
|
+
read = readEventsWithRotations(repoRoot, eventsFile ? { filePath: eventsFile } : {});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return { trees: [], malformed: 0, invalid: 0, complete: null, error: String(err?.message ?? err) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
const trees = [];
|
|
82
|
+
let invalid = 0;
|
|
83
|
+
for (const rec of read.events) {
|
|
84
|
+
if (rec?.event !== SESSION_WORK_TREE_EVENT || rec.session_id !== sessionId) continue;
|
|
85
|
+
const { tree, start_ref: startRef } = rec;
|
|
86
|
+
if (typeof tree !== 'string' || !path.isAbsolute(tree)
|
|
87
|
+
|| typeof startRef !== 'string' || !FULL_SHA_RE.test(startRef)) {
|
|
88
|
+
invalid += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (seen.has(tree)) continue; // first record per tree wins (#1659)
|
|
92
|
+
seen.add(tree);
|
|
93
|
+
trees.push({ tree, start_ref: startRef });
|
|
94
|
+
}
|
|
95
|
+
return { trees, malformed: read.malformed_lines, invalid, complete: read.complete };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run git with an argv array (no shell) and a timeout; never throws.
|
|
100
|
+
*
|
|
101
|
+
* @param {string[]} args
|
|
102
|
+
* @returns {{ ok: true, stdout: string } | { ok: false, error: string }}
|
|
103
|
+
*/
|
|
104
|
+
function git(args) {
|
|
105
|
+
try {
|
|
106
|
+
const stdout = execFileSync('git', args, {
|
|
107
|
+
encoding: 'utf8',
|
|
108
|
+
timeout: GIT_TIMEOUT_MS,
|
|
109
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
110
|
+
});
|
|
111
|
+
return { ok: true, stdout };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
const stderr = typeof err?.stderr === 'string' ? err.stderr.trim() : '';
|
|
114
|
+
return { ok: false, error: stderr || String(err?.message ?? err) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Files each tree's commits touched since that tree's own start ref.
|
|
120
|
+
*
|
|
121
|
+
* For every `{ tree, start_ref }`: `git -C <tree> diff --name-only
|
|
122
|
+
* <start_ref>..HEAD`, run in THAT tree with THAT tree's ref — never a diff
|
|
123
|
+
* between two trees. Paths are tree-relative, as git prints them; `union` is
|
|
124
|
+
* the deduped, sorted union of those relative paths across trees (trees of the
|
|
125
|
+
* same repo share a path space).
|
|
126
|
+
*
|
|
127
|
+
* A tree whose directory is gone, or whose ref no longer resolves there (gc'd,
|
|
128
|
+
* different repo), lands in `skipped` with a reason — the caller decides what
|
|
129
|
+
* an unmeasured tree means; it is never silently dropped and never throws.
|
|
130
|
+
*
|
|
131
|
+
* @param {{ trees: Array<{ tree: string, start_ref: string }> }} args
|
|
132
|
+
* @returns {{ perTree: Array<{ tree: string, start_ref: string, files: string[] }>,
|
|
133
|
+
* skipped: Array<{ tree: string, start_ref: string, reason: string }>,
|
|
134
|
+
* union: string[] }}
|
|
135
|
+
*/
|
|
136
|
+
export function sessionCommitFiles({ trees } = {}) {
|
|
137
|
+
const perTree = [];
|
|
138
|
+
const skipped = [];
|
|
139
|
+
const union = new Set();
|
|
140
|
+
for (const entry of Array.isArray(trees) ? trees : []) {
|
|
141
|
+
const tree = entry?.tree;
|
|
142
|
+
const startRef = entry?.start_ref;
|
|
143
|
+
if (typeof tree !== 'string' || typeof startRef !== 'string' || !FULL_SHA_RE.test(startRef)) {
|
|
144
|
+
skipped.push({ tree: String(tree), start_ref: String(startRef), reason: 'invalid-entry' });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!existsSync(tree)) {
|
|
148
|
+
skipped.push({ tree, start_ref: startRef, reason: 'tree-missing' });
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const verify = git(['-C', tree, 'rev-parse', '--verify', '--quiet', `${startRef}^{commit}`]);
|
|
152
|
+
if (!verify.ok) {
|
|
153
|
+
skipped.push({ tree, start_ref: startRef, reason: 'ref-unresolvable' });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const diff = git(['-C', tree, 'diff', '--name-only', `${startRef}..HEAD`]);
|
|
157
|
+
if (!diff.ok) {
|
|
158
|
+
skipped.push({ tree, start_ref: startRef, reason: `diff-failed: ${diff.error.split('\n')[0]}` });
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const files = diff.stdout.split('\n').filter((l) => l !== '');
|
|
162
|
+
for (const f of files) union.add(f);
|
|
163
|
+
perTree.push({ tree, start_ref: startRef, files });
|
|
164
|
+
}
|
|
165
|
+
return { perTree, skipped, union: [...union].sort() };
|
|
166
|
+
}
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
|
|
102
102
|
import fs from 'node:fs';
|
|
103
103
|
import path from 'node:path';
|
|
104
|
+
import { resolveEventsLedgerRoot } from './platform.mjs';
|
|
104
105
|
|
|
105
106
|
const SESSIONS_REL = ['.orchestrator', 'metrics', 'sessions.jsonl'];
|
|
106
107
|
|
|
@@ -213,6 +214,51 @@ export function isBackfillStub(rec) {
|
|
|
213
214
|
return !hasWork;
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
/** Session types a record must carry (or show work) to count as measured. */
|
|
218
|
+
const MEASURED_SESSION_TYPES = new Set(['housekeeping', 'feature', 'deep']);
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* True when a record is a MEASURED session — one a decision may count:
|
|
222
|
+
* "sessions", deep share, close rate, staleness, dispatcher ranking, site
|
|
223
|
+
* numbers, cold-start history. A record is measured when it is a parseable
|
|
224
|
+
* object, is NOT a backfill stub (`isBackfillStub`), does NOT carry
|
|
225
|
+
* `_synthetic: true`, and EITHER carries a known `session_type`
|
|
226
|
+
* (`housekeeping|feature|deep`) OR shows at least one wave/agent.
|
|
227
|
+
*
|
|
228
|
+
* Why: 59.1 % of fleet session records are synthetic backfill stubs written by
|
|
229
|
+
* `hooks/on-session-end.mjs` via `session-close-backfill.mjs::synthesizeRecord`
|
|
230
|
+
* (`_synthetic: true`, `session_type: 'unknown'`, 0 waves/agents) — measured by
|
|
231
|
+
* D1 on 2026-10-12 (audit E1, `docs/audits/2026-10-12-portfolio-analyse/`).
|
|
232
|
+
* `isRealSession` (`session-schema/filters.mjs`) only drops `status:
|
|
233
|
+
* 'abandoned'`, so a `state-md-completed` stub with `status: 'completed'` still
|
|
234
|
+
* read as a real session; a stub-only ledger then counted as session history.
|
|
235
|
+
* This predicate is the narrower READER question; it does not replace
|
|
236
|
+
* `isRealSession` (whose fail-open contract schema/render readers rely on).
|
|
237
|
+
* Decision readers use this definition alone, including legacy known-type
|
|
238
|
+
* records without a completed status. The staleness banner separately retains
|
|
239
|
+
* unmeasured starts as activity anchors, never as measured completion times.
|
|
240
|
+
*
|
|
241
|
+
* CEILING (BV-004): a legacy pre-4.0.0 record with no `session_type` but with
|
|
242
|
+
* waves counts as measured — waves are evidence of real work, the missing type
|
|
243
|
+
* is only a schema gap. Conversely a known-type record with 0 waves (a
|
|
244
|
+
* coordinator-direct housekeeping close) counts too.
|
|
245
|
+
* Revisit-Trigger: if a writer starts emitting unknown-type records WITH waves
|
|
246
|
+
* that are not real work, or `_synthetic` records stop carrying 0 waves.
|
|
247
|
+
*
|
|
248
|
+
* @param {unknown} rec
|
|
249
|
+
* @returns {boolean}
|
|
250
|
+
*/
|
|
251
|
+
export function isMeasuredSession(rec) {
|
|
252
|
+
if (!isRecordObject(rec)) return false;
|
|
253
|
+
if (isBackfillStub(rec) || rec._synthetic === true) return false;
|
|
254
|
+
if (MEASURED_SESSION_TYPES.has(rec.session_type)) return true;
|
|
255
|
+
return (Array.isArray(rec.waves) && rec.waves.length > 0)
|
|
256
|
+
|| rec.total_waves > 0 || rec.total_agents > 0
|
|
257
|
+
// Legacy records may carry measured agent counts only in agent_summary.
|
|
258
|
+
|| (isRecordObject(rec.agent_summary)
|
|
259
|
+
&& ['complete', 'partial', 'failed', 'spiral'].some((key) => rec.agent_summary[key] > 0));
|
|
260
|
+
}
|
|
261
|
+
|
|
216
262
|
/**
|
|
217
263
|
* Rule (1), cross-id half (#1443) — drop every backfill stub that shares a
|
|
218
264
|
* non-empty `raw_session_id` with at least one non-stub. A group of stubs only
|
|
@@ -463,10 +509,16 @@ export function canonicalizeSessions(records, opts) {
|
|
|
463
509
|
* so id-less records are counted as-is and only the id-bearing ones go through
|
|
464
510
|
* the identity collapse.
|
|
465
511
|
*
|
|
512
|
+
* `measuredOnly` drops every record `isMeasuredSession` rejects (backfill
|
|
513
|
+
* stubs, `_synthetic`, unknown type without work) — for a reader whose count
|
|
514
|
+
* is a "has this repo real session history?" decision (cold-start, audit E1).
|
|
515
|
+
*
|
|
466
516
|
* @param {string} raw — full file contents.
|
|
517
|
+
* @param {object} [opts]
|
|
518
|
+
* @param {boolean} [opts.measuredOnly=false]
|
|
467
519
|
* @returns {number}
|
|
468
520
|
*/
|
|
469
|
-
export function countSessionsInJsonl(raw) {
|
|
521
|
+
export function countSessionsInJsonl(raw, { measuredOnly = false } = {}) {
|
|
470
522
|
if (typeof raw !== 'string' || raw.length === 0) return 0;
|
|
471
523
|
const parsed = [];
|
|
472
524
|
for (const line of raw.split('\n')) {
|
|
@@ -479,6 +531,12 @@ export function countSessionsInJsonl(raw) {
|
|
|
479
531
|
}
|
|
480
532
|
}
|
|
481
533
|
const identified = parsed.filter((r) => isRecordObject(r) && isNonEmptyString(r.session_id));
|
|
534
|
+
if (measuredOnly) {
|
|
535
|
+
const anonymousMeasured = parsed.filter(
|
|
536
|
+
(r) => isMeasuredSession(r) && !isNonEmptyString(r.session_id),
|
|
537
|
+
).length;
|
|
538
|
+
return canonicalizeSessions(identified).filter(isMeasuredSession).length + anonymousMeasured;
|
|
539
|
+
}
|
|
482
540
|
const anonymous = parsed.length - identified.length;
|
|
483
541
|
return canonicalizeSessions(identified).length + anonymous;
|
|
484
542
|
}
|
|
@@ -515,7 +573,7 @@ export function countSessionsInJsonl(raw) {
|
|
|
515
573
|
*
|
|
516
574
|
* @param {object} [args]
|
|
517
575
|
* @param {string} [args.repoRoot] project root; the ledger is resolved as
|
|
518
|
-
*
|
|
576
|
+
* the main checkout's `.orchestrator/metrics/sessions.jsonl`. Defaults to
|
|
519
577
|
* `process.cwd()` when neither this nor `filePath` is given.
|
|
520
578
|
* @param {string} [args.filePath] explicit ledger path (wins over `repoRoot`).
|
|
521
579
|
* @returns {Array<object>} canonical records
|
|
@@ -523,7 +581,7 @@ export function countSessionsInJsonl(raw) {
|
|
|
523
581
|
export function readCanonicalSessions({ repoRoot, filePath } = {}) {
|
|
524
582
|
const resolved = isNonEmptyString(filePath)
|
|
525
583
|
? filePath
|
|
526
|
-
: path.join(isNonEmptyString(repoRoot) ? repoRoot : process.cwd(), ...SESSIONS_REL);
|
|
584
|
+
: path.join(resolveEventsLedgerRoot(isNonEmptyString(repoRoot) ? repoRoot : process.cwd()), ...SESSIONS_REL);
|
|
527
585
|
|
|
528
586
|
let raw;
|
|
529
587
|
try {
|
|
@@ -22,39 +22,13 @@
|
|
|
22
22
|
* entry point that is COMPLETELY try/catch-wrapped (never throws) and returns
|
|
23
23
|
* either `null` (silent no-op) or `{severity:'warn'|'alert', message, ...extra}`.
|
|
24
24
|
*
|
|
25
|
-
* TWO
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* explicitly in `OPTIONAL_FIELDS` (`session-schema/constants.mjs`) rather
|
|
33
|
-
* than inferable only from an `if` — so a record vault-mirror refuses to
|
|
34
|
-
* render can pass it cleanly.
|
|
35
|
-
* - vault-mirror's render path — its own, differently-shaped requirement
|
|
36
|
-
* set (`RENDERABLE_SESSION_FIELDS_V1` / `_V2` / `_V3`, exported from
|
|
37
|
-
* `scripts/lib/vault-mirror/render-sessions.mjs` and consumed by the
|
|
38
|
-
* matching `generateSessionNote*` generator, routed by
|
|
39
|
-
* `detectSessionSchema`), which REQUIRES `effectiveness` to be present.
|
|
40
|
-
* A record can therefore be schema-valid and still get no vault note.
|
|
41
|
-
* Since #964 those lists are module-scope and pinned to the write-path
|
|
42
|
-
* schema by a mechanical superset test (v1 ⊇ `REQUIRED_FIELDS`, with
|
|
43
|
-
* measured carve-outs for the write-unreachable v2/v3) — so "renderable"
|
|
44
|
-
* is now a stated strengthening of "schema-valid" rather than an
|
|
45
|
-
* undeclared second opinion. That relationship is exactly why BOTH
|
|
46
|
-
* populations still have to be reported: a superset can still exclude.
|
|
47
|
-
*
|
|
48
|
-
* Reporting one population would hide the other, so the banner names both and
|
|
49
|
-
* attributes each to its own consequence.
|
|
50
|
-
*
|
|
51
|
-
* Live ledger at HEAD 730ee9d (measured 2026-08-03; 205 parseable records,
|
|
52
|
-
* 146 of them `status: 'abandoned'`, all 205 routing to the v1 generator):
|
|
53
|
-
* `validateSession` fails 0, vault-mirror render fails 10 — and all 10 of
|
|
54
|
-
* those are abandoned, i.e. discarded by the #909 filter BEFORE the render
|
|
55
|
-
* path (see `mirrorSkipReason` below), so the banner is correctly silent
|
|
56
|
-
* here. The counts move as the ledger grows; treat them as a dated
|
|
57
|
-
* measurement, never as an invariant — re-run the probe before quoting them.
|
|
25
|
+
* TWO CONTRACTS OVER ONE STORE — the banner reports schema failures and
|
|
26
|
+
* actual mirror failures independently. Effectiveness is optional/nullable in
|
|
27
|
+
* both contracts (#1575); its absence now renders unknown metrics instead of
|
|
28
|
+
* losing a note. Legacy V2/V3 shapes remain outside the canonical writer's
|
|
29
|
+
* schema even when the mirror renders them. A renderer-only failure does not
|
|
30
|
+
* prove the record bypassed the writer, so the remedy must name the observed
|
|
31
|
+
* failure rather than infer its provenance.
|
|
58
32
|
*
|
|
59
33
|
* The vault-mirror population is measured by CALLING THE REAL RENDER PATH in
|
|
60
34
|
* a try/catch — never by re-deriving its required-field list here. A third
|
|
@@ -92,6 +66,7 @@
|
|
|
92
66
|
import { existsSync, readFileSync } from 'node:fs';
|
|
93
67
|
import path from 'node:path';
|
|
94
68
|
|
|
69
|
+
import { resolveEventsLedgerRoot } from './platform.mjs';
|
|
95
70
|
import { isRealSession } from './session-schema/filters.mjs';
|
|
96
71
|
import { validateSession } from './session-schema/validator.mjs';
|
|
97
72
|
import {
|
|
@@ -224,7 +199,9 @@ export function checkSessionsIntegrity({ repoRoot } = {}) {
|
|
|
224
199
|
try {
|
|
225
200
|
if (!repoRoot || typeof repoRoot !== 'string') return null;
|
|
226
201
|
|
|
227
|
-
|
|
202
|
+
// #1592: the repo's ONE ledger — the main checkout's for a linked worktree,
|
|
203
|
+
// where every writer appends. The worktree has none since #1584.
|
|
204
|
+
const lines = readJsonlLines(path.join(resolveEventsLedgerRoot(repoRoot), SESSIONS_PATH));
|
|
228
205
|
if (lines === null || lines.length === 0) return null;
|
|
229
206
|
|
|
230
207
|
/** @type {{line: number, sessionId: string, error: string}[]} */
|
|
@@ -284,11 +261,12 @@ export function checkSessionsIntegrity({ repoRoot } = {}) {
|
|
|
284
261
|
);
|
|
285
262
|
}
|
|
286
263
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
264
|
+
const remedy = schemaInvalid.length > 0
|
|
265
|
+
? 'Inspect schema-invalid records with validateSession; preview repairs with ' +
|
|
266
|
+
'node scripts/repair-invalid-sessions.mjs --dry-run; absolute --apply requires ' +
|
|
267
|
+
'an explicit --repo-root equal to the canonical current Git top-level (no trailing slash or ..)'
|
|
268
|
+
: 'Inspect the reported vault-mirror rendering errors; schema-valid records need no schema repair';
|
|
269
|
+
const base = `sessions-integrity: ${parts.join('; ')} — ${remedy}`;
|
|
292
270
|
|
|
293
271
|
const message = severity === 'alert' ? `🚨 ${base}.` : `⚠ ${base}.`;
|
|
294
272
|
|
|
@@ -146,7 +146,8 @@ import path from 'node:path';
|
|
|
146
146
|
import { eventsFilePath } from './events.mjs';
|
|
147
147
|
import { readLock, DEFAULT_TTL_HOURS } from './session-lock.mjs';
|
|
148
148
|
import { isRealSession } from './session-schema/filters.mjs';
|
|
149
|
-
import { readCanonicalSessions } from './sessions-canonical.mjs';
|
|
149
|
+
import { isMeasuredSession, readCanonicalSessions } from './sessions-canonical.mjs';
|
|
150
|
+
import { resolveEventsLedgerRoot } from './platform.mjs';
|
|
150
151
|
|
|
151
152
|
/** Repo-relative path to the session ledger (one record per closed session). */
|
|
152
153
|
const SESSIONS_PATH = '.orchestrator/metrics/sessions.jsonl';
|
|
@@ -238,11 +239,12 @@ function readJsonlLines(filePath) {
|
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
/**
|
|
241
|
-
* True when a `sessions.jsonl` record
|
|
242
|
-
*
|
|
242
|
+
* True when a `sessions.jsonl` record cannot supply a measured completion —
|
|
243
|
+
* unmeasured records retain only their `started_at` activity anchor (module header,
|
|
243
244
|
* "Backfill-stub self-erasure fix").
|
|
244
245
|
*
|
|
245
|
-
* `
|
|
246
|
+
* `isMeasuredSession()` supplies the shared measured population. The legacy
|
|
247
|
+
* anchor rule also catches `status:
|
|
246
248
|
* 'abandoned'`, the canonical phantom marker. A `_backfill_source` counts only
|
|
247
249
|
* when the status is not `completed`: the `state-md-completed` supersede record
|
|
248
250
|
* is the session's own completion claim. Same exclusion as
|
|
@@ -259,6 +261,7 @@ function readJsonlLines(filePath) {
|
|
|
259
261
|
* @returns {boolean}
|
|
260
262
|
*/
|
|
261
263
|
function isNonAnchorStub(record) {
|
|
264
|
+
if (!isMeasuredSession(record)) return true;
|
|
262
265
|
if (!isRealSession(record)) return true;
|
|
263
266
|
return typeof record._backfill_source === 'string' && record._backfill_source.length > 0
|
|
264
267
|
&& record.status !== 'completed';
|
|
@@ -515,7 +518,7 @@ export function checkSessionsStaleness({ repoRoot, now = Date.now() } = {}) {
|
|
|
515
518
|
|
|
516
519
|
const nowMs = typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
|
|
517
520
|
|
|
518
|
-
const sessionsPath = path.join(repoRoot, SESSIONS_PATH);
|
|
521
|
+
const sessionsPath = path.join(resolveEventsLedgerRoot(repoRoot), SESSIONS_PATH);
|
|
519
522
|
const sessionLines = readJsonlLines(sessionsPath);
|
|
520
523
|
if (sessionLines === null || sessionLines.length === 0) return null;
|
|
521
524
|
|
package/scripts/lib/sh-quote.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sh-quote.mjs — the one POSIX shell-quoting helper (#1438).
|
|
3
|
+
* POSIX-only: this helper does not quote cmd.exe commands.
|
|
3
4
|
*
|
|
4
5
|
* Four byte-identical private copies (cursor-install, tmux-layout/layouts,
|
|
5
6
|
* tmux-layout/tmux-shell, run-quality-gate) and one inline copy
|
|
@@ -53,6 +53,7 @@ import { join } from 'node:path';
|
|
|
53
53
|
import { createInterface } from 'node:readline';
|
|
54
54
|
|
|
55
55
|
import { readLock, isLockLive } from './session-lock.mjs';
|
|
56
|
+
import { readProcessLocalSessionIds } from './session-identity/own-session.mjs';
|
|
56
57
|
|
|
57
58
|
// ---------------------------------------------------------------------------
|
|
58
59
|
// Constants (every ceiling named, per .claude/rules/build-value.md BV-004)
|
|
@@ -693,7 +694,10 @@ export function resolveRawSessionId({ repoRoot, env = process.env } = {}) {
|
|
|
693
694
|
}
|
|
694
695
|
if (!lock || !isLockLive(lock)) return null;
|
|
695
696
|
const fromLock = typeof lock.session_id === 'string' ? lock.session_id.trim() : '';
|
|
696
|
-
|
|
697
|
+
// The repo-global lock may name a PEER (#1664, class #1663): count it only
|
|
698
|
+
// when it is among this process's own ids, never as a bare fallback.
|
|
699
|
+
if (!fromLock || !readProcessLocalSessionIds({ env }).includes(fromLock)) return null;
|
|
700
|
+
return { sessionId: fromLock, source: 'session.lock' };
|
|
697
701
|
}
|
|
698
702
|
|
|
699
703
|
/**
|
|
@@ -34,17 +34,17 @@ import { promises as fs } from 'node:fs';
|
|
|
34
34
|
import path from 'node:path';
|
|
35
35
|
import { fileURLToPath } from 'node:url';
|
|
36
36
|
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
37
|
+
import { isMeasuredSession, readCanonicalSessions } from '../sessions-canonical.mjs';
|
|
38
|
+
import { resolveEventsLedgerRoot } from '../platform.mjs';
|
|
39
39
|
|
|
40
40
|
const DEFAULT_INVOCATIONS_PATH = path.resolve(
|
|
41
41
|
fileURLToPath(import.meta.url),
|
|
42
42
|
'../../../../.orchestrator/metrics/skill-invocations.jsonl',
|
|
43
43
|
);
|
|
44
44
|
|
|
45
|
-
const DEFAULT_SESSIONS_PATH = path.
|
|
46
|
-
fileURLToPath(import.meta.url),
|
|
47
|
-
'
|
|
45
|
+
const DEFAULT_SESSIONS_PATH = path.join(
|
|
46
|
+
resolveEventsLedgerRoot(fileURLToPath(new URL('../../../', import.meta.url))),
|
|
47
|
+
'.orchestrator', 'metrics', 'sessions.jsonl',
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -78,8 +78,8 @@ async function readJsonl(filePath) {
|
|
|
78
78
|
/**
|
|
79
79
|
* Builds a Map<session_id, { agentSummary, real }> from sessions.jsonl records.
|
|
80
80
|
* Records without a session_id or with a non-object agent_summary are skipped.
|
|
81
|
-
* `real`
|
|
82
|
-
* it to route
|
|
81
|
+
* `real` uses the shared measured-session definition (#1584) — callers use
|
|
82
|
+
* it to route unmeasured joins to the legacy `abandoned` outcome bucket instead of counting
|
|
83
83
|
* a zero-signal join as `sessionsJoined`.
|
|
84
84
|
*
|
|
85
85
|
* Callers pass `sessionRecords` from `readCanonicalSessions()` (#1209b) — one
|
|
@@ -99,7 +99,7 @@ function buildSessionMap(sessionRecords) {
|
|
|
99
99
|
for (const rec of sessionRecords) {
|
|
100
100
|
if (typeof rec.session_id !== 'string' || !rec.session_id) continue;
|
|
101
101
|
if (rec.agent_summary && typeof rec.agent_summary === 'object') {
|
|
102
|
-
map.set(rec.session_id, { agentSummary: rec.agent_summary, real:
|
|
102
|
+
map.set(rec.session_id, { agentSummary: rec.agent_summary, real: isMeasuredSession(rec) });
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
return map;
|
|
@@ -186,9 +186,10 @@ export async function joinSkillOutcomes({
|
|
|
186
186
|
record.outcomes.spiral += typeof summary.spiral === 'number' ? summary.spiral : 0;
|
|
187
187
|
sessionsJoined += 1;
|
|
188
188
|
} else if (entry && !entry.real) {
|
|
189
|
-
// Found in sessions.jsonl but
|
|
189
|
+
// Found in sessions.jsonl but unmeasured (#1584) — a
|
|
190
190
|
// zero-signal join. Route to a distinct bucket instead of inflating
|
|
191
191
|
// sessionsJoined or conflating with "not found at all" (unknown).
|
|
192
|
+
// Keep the legacy output keys; this bucket includes completed synthetic stubs.
|
|
192
193
|
record.outcomes.abandoned += 1;
|
|
193
194
|
sessionsAbandoned += 1;
|
|
194
195
|
} else {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import { readFile, appendFile, mkdir } from 'node:fs/promises';
|
|
23
23
|
import { existsSync } from 'node:fs';
|
|
24
24
|
import path from 'node:path';
|
|
25
|
+
import { resolveLedgerPath } from './ledger-path.mjs';
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Constants
|
|
@@ -36,6 +37,18 @@ export const VALID_EVENTS = Object.freeze(['selected']);
|
|
|
36
37
|
/** Default JSONL path for skill invocation records. */
|
|
37
38
|
export const DEFAULT_SKILL_INVOCATIONS_PATH = '.orchestrator/metrics/skill-invocations.jsonl';
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Absolute path of the skill-invocations ledger for a writer or reader rooted
|
|
42
|
+
* at `root` (#1722): the main checkout's copy when `root` is a linked worktree
|
|
43
|
+
* of a repo that ignores the ledger, otherwise `<root>/` + the default path.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} [root] repo root; default `getProjectDir()`
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
export function defaultSkillInvocationsPath(root) {
|
|
49
|
+
return resolveLedgerPath(DEFAULT_SKILL_INVOCATIONS_PATH, root);
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
// ---------------------------------------------------------------------------
|
|
40
53
|
// ValidationError
|
|
41
54
|
// ---------------------------------------------------------------------------
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
import { readFile, appendFile, mkdir } from 'node:fs/promises';
|
|
30
30
|
import { existsSync } from 'node:fs';
|
|
31
31
|
import path from 'node:path';
|
|
32
|
+
import { resolveLedgerPath } from './ledger-path.mjs';
|
|
32
33
|
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
// Constants
|
|
@@ -215,7 +216,10 @@ export function normalizeSkillJudgment(entry) {
|
|
|
215
216
|
* @returns {Promise<object>} the validated + normalized entry that was written
|
|
216
217
|
* @throws {ValidationError} when the entry fails schema validation
|
|
217
218
|
*/
|
|
218
|
-
export async function appendSkillJudgment(
|
|
219
|
+
export async function appendSkillJudgment(
|
|
220
|
+
record,
|
|
221
|
+
{ path: filePath = resolveLedgerPath(DEFAULT_SKILL_JUDGMENTS_PATH, process.cwd()) } = {},
|
|
222
|
+
) {
|
|
219
223
|
const stamped = {
|
|
220
224
|
...record,
|
|
221
225
|
schema_version: record?.schema_version ?? CURRENT_SCHEMA_VERSION,
|
|
@@ -241,7 +245,7 @@ export async function appendSkillJudgment(record, { path: filePath = DEFAULT_SKI
|
|
|
241
245
|
* @param {string} [filePath] — absolute path to target .jsonl file
|
|
242
246
|
* @returns {Promise<object[]>} array of normalized skill-judgment records
|
|
243
247
|
*/
|
|
244
|
-
export async function readSkillJudgments(filePath = DEFAULT_SKILL_JUDGMENTS_PATH) {
|
|
248
|
+
export async function readSkillJudgments(filePath = resolveLedgerPath(DEFAULT_SKILL_JUDGMENTS_PATH, process.cwd())) {
|
|
245
249
|
if (!existsSync(filePath)) return [];
|
|
246
250
|
let raw;
|
|
247
251
|
try {
|