session-orchestrator 5.12.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 +2 -2
- 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 +162 -4
- package/README.md +126 -127
- 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 +1 -1
- 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 +90 -17
- 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 +49 -15
- package/docs/session-config-template.md +30 -22
- 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 +139 -49
- 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 -2
- 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 +39 -27
- 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/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 +2 -2
- 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 +4 -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-id.mjs +9 -1
- package/scripts/lib/session-identity/own-session.mjs +63 -0
- package/scripts/lib/session-lock.mjs +114 -0
- package/scripts/lib/session-record-repair.mjs +245 -35
- package/scripts/lib/session-registry.mjs +72 -9
- 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 +49 -5
- package/scripts/lib/so-hud/runtime.mjs +15 -4
- 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 +14 -5
- package/scripts/lib/state-md/mission-status.mjs +5 -1
- 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 +19 -7
- package/scripts/lib/vault-mirror/process.mjs +56 -27
- package/scripts/lib/vault-mirror/prune.mjs +5 -2
- package/scripts/lib/vault-mirror/render-learnings.mjs +3 -3
- 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 +79 -0
- package/scripts/lib/vault-staleness-banner.mjs +4 -7
- package/scripts/lib/vault-status/board-writer.mjs +2 -1
- 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 +10 -17
- 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 +29 -6
- package/scripts/state-md-idle-reset.mjs +2 -1
- 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/skills/_shared/fleet-protocol.md +142 -1
- 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 +3 -2
- package/skills/architecture/references/domain-model.md +1 -1
- 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 +94 -11
- 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-documentation-updates.md +14 -12
- package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
- package/skills/session-end/references/session-summary-template.md +4 -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 +9 -3
- package/skills/session-start/phase-4-5-resource-health.md +11 -5
- 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 +2 -2
- 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/assets/og-card.png +0 -0
- package/assets/og-card.svg +0 -47
- package/assets/wave-lifecycle.svg +0 -98
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* close-precheck.mjs — classify `<state-dir>/STATE.md` before `/close` hands
|
|
4
|
+
* off to session-end (#1661). Logic: `lib/session-end/close-precheck.mjs`.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/close-precheck.mjs [--repo-root <dir>] [--state-dir <dir>] [--confirm-own <token>]
|
|
8
|
+
*
|
|
9
|
+
* `--confirm-own` takes the 16-hex `confirmToken` of an `ownership-unproven`
|
|
10
|
+
* result and confirms that STATE.md as this session's while unchanged (#1718).
|
|
11
|
+
*
|
|
12
|
+
* Prints one JSON object: `verdict`, `runsSessionEnd`, `ownership`, `status`,
|
|
13
|
+
* `stateSessionId`, `ownSessionIds`, `statePath`, optional `reason`; with
|
|
14
|
+
* `ownership-unproven` also `wouldRun`, `confirmToken`, `session`, `startedAt`.
|
|
15
|
+
* Exit 0 for every verdict (a stop is an answer, not an error; Claude Code
|
|
16
|
+
* marks every non-zero exit as a tool failure) · 2 usage error.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { classifyCloseState, RUNS_SESSION_END } from './lib/session-end/close-precheck.mjs';
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const out = { repoRoot: process.cwd(), stateDir: undefined, confirmToken: undefined };
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
const a = argv[i];
|
|
25
|
+
if (a === '--repo-root' || a === '--state-dir') {
|
|
26
|
+
const v = argv[++i];
|
|
27
|
+
if (!v) throw new Error(`${a} needs a value`);
|
|
28
|
+
if (a === '--repo-root') out.repoRoot = v; else out.stateDir = v;
|
|
29
|
+
} else if (a === '--confirm-own') {
|
|
30
|
+
const v = argv[++i];
|
|
31
|
+
if (!v || !/^[0-9a-f]{16}$/.test(v)) throw new Error('--confirm-own needs a 16-hex token');
|
|
32
|
+
out.confirmToken = v;
|
|
33
|
+
} else if (a === '--help' || a === '-h') {
|
|
34
|
+
out.help = true;
|
|
35
|
+
} else {
|
|
36
|
+
throw new Error(`unknown argument: ${a}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let args;
|
|
43
|
+
try {
|
|
44
|
+
args = parseArgs(process.argv.slice(2));
|
|
45
|
+
} catch (err) {
|
|
46
|
+
process.stderr.write(`close-precheck: ${err.message}\n`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
if (args.help) {
|
|
50
|
+
process.stdout.write('Usage: node scripts/close-precheck.mjs [--repo-root <dir>] [--state-dir <dir>] [--confirm-own <token>]\n');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = classifyCloseState({ repoRoot: args.repoRoot, stateDir: args.stateDir, confirmToken: args.confirmToken });
|
|
55
|
+
process.stdout.write(`${JSON.stringify({ ...result, runsSessionEnd: RUNS_SESSION_END.has(result.verdict) })}\n`);
|
|
@@ -46,10 +46,11 @@ import { join } from 'node:path';
|
|
|
46
46
|
import { randomBytes } from 'node:crypto';
|
|
47
47
|
|
|
48
48
|
import { readPeerCards } from './lib/peer-cards/reader.mjs';
|
|
49
|
-
import {
|
|
50
|
-
import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
|
|
49
|
+
import { readCanonicalSessions, isMeasuredSession } from './lib/sessions-canonical.mjs';
|
|
51
50
|
import { recordDialecticRun } from './lib/learnings/evolve-telemetry.mjs';
|
|
52
51
|
import { DEFAULT_BUDGET_TOKENS } from './lib/config/dialectic.mjs';
|
|
52
|
+
import { withVisibleInboxText } from './lib/learnings/surface.mjs';
|
|
53
|
+
import { resolveLedgerPath } from './lib/ledger-path.mjs';
|
|
53
54
|
|
|
54
55
|
// ---------------------------------------------------------------------------
|
|
55
56
|
// Constants
|
|
@@ -400,17 +401,24 @@ function parseTargetFromInfo(info) {
|
|
|
400
401
|
* Best-effort read of a JSONL file: line-by-line JSON.parse with malformed-
|
|
401
402
|
* line skip. Returns [] when the file is missing or empty.
|
|
402
403
|
*
|
|
404
|
+
* For the canonical `learnings.jsonl` the visible records of a sibling
|
|
405
|
+
* `learnings-inbox.jsonl` are appended (#1516, #1685) via the shared
|
|
406
|
+
* visibility rule in `withVisibleInboxText` — also when the live file is
|
|
407
|
+
* absent (inbox host). An unreadable inbox never costs the live read.
|
|
408
|
+
*
|
|
403
409
|
* @param {string} absPath
|
|
404
410
|
* @returns {Promise<Array<object>>}
|
|
405
411
|
*/
|
|
406
412
|
async function readJsonlBestEffort(absPath) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
+
let raw = '';
|
|
414
|
+
if (existsSync(absPath)) {
|
|
415
|
+
try {
|
|
416
|
+
raw = await readFile(absPath, 'utf8');
|
|
417
|
+
} catch {
|
|
418
|
+
return [];
|
|
419
|
+
}
|
|
413
420
|
}
|
|
421
|
+
raw = await withVisibleInboxText(absPath, raw);
|
|
414
422
|
/** @type {Array<object>} */
|
|
415
423
|
const out = [];
|
|
416
424
|
for (const line of raw.split('\n')) {
|
|
@@ -435,7 +443,7 @@ async function readJsonlBestEffort(absPath) {
|
|
|
435
443
|
* @returns {Promise<Array<object>>}
|
|
436
444
|
*/
|
|
437
445
|
async function readTopLearnings(repoRoot, topN) {
|
|
438
|
-
const path =
|
|
446
|
+
const path = resolveLedgerPath('.orchestrator/metrics/learnings.jsonl', repoRoot); // #1722
|
|
439
447
|
const entries = await readJsonlBestEffort(path);
|
|
440
448
|
entries.sort((a, b) => {
|
|
441
449
|
const ca = typeof a?.confidence === 'number' ? a.confidence : 0;
|
|
@@ -451,8 +459,8 @@ async function readTopLearnings(repoRoot, topN) {
|
|
|
451
459
|
/**
|
|
452
460
|
* Read and rank sessions: collapse ledger duplicates (#1209 — the ledger is
|
|
453
461
|
* append-only and can carry more than one raw line per physical session, see
|
|
454
|
-
* `scripts/lib/sessions-canonical.mjs` header),
|
|
455
|
-
*
|
|
462
|
+
* `scripts/lib/sessions-canonical.mjs` header), retain measured work
|
|
463
|
+
* via isMeasuredSession (#1584), excluding synthetic completed stubs, sort by
|
|
456
464
|
* `completed_at` DESC, take last K.
|
|
457
465
|
*
|
|
458
466
|
* Without the dedup, a duplicate ledger line for the SAME session can occupy
|
|
@@ -467,7 +475,7 @@ async function readTopLearnings(repoRoot, topN) {
|
|
|
467
475
|
* @returns {Array<object>}
|
|
468
476
|
*/
|
|
469
477
|
function readLastSessions(repoRoot, lastK) {
|
|
470
|
-
const entries =
|
|
478
|
+
const entries = readCanonicalSessions({ repoRoot }).filter(isMeasuredSession);
|
|
471
479
|
entries.sort((a, b) => {
|
|
472
480
|
const ta = typeof a?.completed_at === 'string' ? a.completed_at : '';
|
|
473
481
|
const tb = typeof b?.completed_at === 'string' ? b.completed_at : '';
|
package/scripts/emit-event.mjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
import { parseArgs } from 'node:util';
|
|
33
33
|
import { dirname, join } from 'node:path';
|
|
34
|
-
import { fileURLToPath } from 'node:url';
|
|
34
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
35
35
|
|
|
36
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
37
|
|
|
@@ -141,7 +141,7 @@ if (payloadRaw !== undefined) {
|
|
|
141
141
|
|
|
142
142
|
let emitEvent;
|
|
143
143
|
try {
|
|
144
|
-
({ emitEvent } = await import(join(__dirname, 'lib', 'events.mjs')));
|
|
144
|
+
({ emitEvent } = await import(pathToFileURL(join(__dirname, 'lib', 'events.mjs')).href));
|
|
145
145
|
} catch (err) {
|
|
146
146
|
fail(`failed to load events.mjs: ${err.message}`, 2, jsonMode);
|
|
147
147
|
}
|
package/scripts/emit-session.mjs
CHANGED
|
@@ -27,17 +27,21 @@
|
|
|
27
27
|
* {"action":"appended","path":"<file>","session_id":"<id>","schema_version":2}
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
30
|
+
import { execFileSync } from 'node:child_process';
|
|
31
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
32
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
33
|
+
import { explainEventsLedgerRoot, resolveEventsLedgerRoot } from './lib/platform.mjs';
|
|
33
34
|
import { appendJsonl } from './lib/common.mjs';
|
|
35
|
+
import { resolveLedgerPath } from './lib/ledger-path.mjs';
|
|
34
36
|
import {
|
|
35
37
|
MEMORY_CLEANUP_EVENT,
|
|
36
38
|
deriveMemoryCleanupSignal,
|
|
37
39
|
stampMemoryCleanup,
|
|
38
40
|
} from './lib/memory-cleanup-stamp.mjs';
|
|
39
|
-
import { parseStateMd, readSessionProfile
|
|
41
|
+
import { parseStateMd, readSessionProfile } from './lib/state-md.mjs';
|
|
42
|
+
import { resolveStateMdWriteTarget, warnForeignStateMd } from './lib/state-md/frontmatter-mutators.mjs';
|
|
40
43
|
import { readProcessLocalSessionIds } from './lib/session-identity/own-session.mjs';
|
|
44
|
+
import { readSessionWorkTrees } from './lib/session-work-trees.mjs';
|
|
41
45
|
import { rollupSessionTokens } from './lib/session-token-rollup.mjs';
|
|
42
46
|
import { COST_BASIS, PRICING_TABLE_DATE } from './lib/telemetry/pricing.mjs';
|
|
43
47
|
import { serializeSessionLineChecked } from './lib/session-schema/serializer.mjs';
|
|
@@ -50,6 +54,7 @@ import {
|
|
|
50
54
|
normalizeWaveKeys,
|
|
51
55
|
} from './lib/session-schema.mjs';
|
|
52
56
|
import { stableHostname } from './lib/host-identity.mjs';
|
|
57
|
+
import { activeDir } from './lib/session-registry.mjs';
|
|
53
58
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
54
59
|
|
|
55
60
|
/**
|
|
@@ -181,18 +186,23 @@ function resolveOwnSessionUuid({ override, recordSessionId }) {
|
|
|
181
186
|
* Whole-file read, like deriveMemoryCleanupSignal() on the same file — fine at
|
|
182
187
|
* today's events.jsonl size; revisit if the file outgrows a single read.
|
|
183
188
|
*
|
|
189
|
+
* `behind` (#1668) is that same event's count of upstream commits the checkout
|
|
190
|
+
* lacked at start: a non-negative integer when measured, null when the event
|
|
191
|
+
* carries none (no upstream, or a pre-#1668 writer) — never read as 0.
|
|
192
|
+
*
|
|
184
193
|
* Never throws.
|
|
185
194
|
*
|
|
186
195
|
* @param {string} eventsFile
|
|
187
196
|
* @param {string} uuid
|
|
188
|
-
* @returns {string|null}
|
|
197
|
+
* @returns {{ headSha: string|null, behind: number|null }}
|
|
189
198
|
*/
|
|
190
199
|
function readOwnStartHeadSha(eventsFile, uuid) {
|
|
200
|
+
const none = { headSha: null, behind: null };
|
|
191
201
|
let raw;
|
|
192
202
|
try {
|
|
193
203
|
raw = readFileSync(eventsFile, 'utf8');
|
|
194
204
|
} catch {
|
|
195
|
-
return
|
|
205
|
+
return none;
|
|
196
206
|
}
|
|
197
207
|
for (const line of raw.split('\n')) {
|
|
198
208
|
if (!line.includes(uuid)) continue;
|
|
@@ -203,14 +213,115 @@ function readOwnStartHeadSha(eventsFile, uuid) {
|
|
|
203
213
|
continue;
|
|
204
214
|
}
|
|
205
215
|
if (ev?.event !== 'orchestrator.session.started' || ev.session_id !== uuid) continue;
|
|
206
|
-
if (ev.native_source === 'compact' || ev.native_source === 'resume') return
|
|
207
|
-
return
|
|
216
|
+
if (ev.native_source === 'compact' || ev.native_source === 'resume') return none;
|
|
217
|
+
return {
|
|
218
|
+
headSha: typeof ev.head_sha === 'string' && FULL_SHA_RE.test(ev.head_sha) ? ev.head_sha : null,
|
|
219
|
+
behind: Number.isInteger(ev.behind) && ev.behind >= 0 ? ev.behind : null,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return none;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* True when THIS session's registry entry proves it works in a different tree
|
|
227
|
+
* than the one it started in (#1652): `work_root` (refreshed at every turn end,
|
|
228
|
+
* #1605) differs from `session_root` (the SessionStart project root, the tree
|
|
229
|
+
* `head_sha` was measured in). Then the start event's `head_sha` names the
|
|
230
|
+
* wrong tree — a `<head_sha>..HEAD` range would attribute that tree's history,
|
|
231
|
+
* not the session's commits — so the fallback must not adopt it.
|
|
232
|
+
*
|
|
233
|
+
* Absent entry, absent roots or an unreadable file → false (no proof of a
|
|
234
|
+
* divergence; the pre-#1652 behaviour). Ceiling: `work_root` follows the
|
|
235
|
+
* harness cwd, so a coordinator that commits in a worktree through absolute
|
|
236
|
+
* paths without changing its cwd is not detected — the primary source is the
|
|
237
|
+
* STATE.md `session-start-ref` (HEAD of the session's own checkout — repoRoot,
|
|
238
|
+
* the tree STATE.md lives in; commits in additional worktrees: #1659).
|
|
239
|
+
*
|
|
240
|
+
* Never throws.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} uuid
|
|
243
|
+
* @returns {boolean}
|
|
244
|
+
*/
|
|
245
|
+
function startTreeDiverged(uuid) {
|
|
246
|
+
if (!/^[A-Za-z0-9._-]+$/.test(uuid) || uuid.startsWith('.')) return false;
|
|
247
|
+
try {
|
|
248
|
+
const entry = JSON.parse(readFileSync(join(activeDir(), `${uuid}.json`), 'utf8'));
|
|
249
|
+
const { session_root: sessionRoot, work_root: workRoot } = entry ?? {};
|
|
250
|
+
return typeof sessionRoot === 'string' && typeof workRoot === 'string' && sessionRoot !== workRoot;
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Realpath, or the resolved path when it does not exist (any more).
|
|
258
|
+
*
|
|
259
|
+
* @param {string} p
|
|
260
|
+
* @returns {string}
|
|
261
|
+
*/
|
|
262
|
+
function realOrResolved(p) {
|
|
263
|
+
try {
|
|
264
|
+
return realpathSync(p);
|
|
265
|
+
} catch {
|
|
266
|
+
return resolve(p);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Keep only well-formed `session_work_trees` entries (#1659): an absolute
|
|
272
|
+
* `tree` and a FULL hex `start_ref` — the shape the work_tree reader accepts.
|
|
273
|
+
* Invalid entries are dropped, never the record.
|
|
274
|
+
*
|
|
275
|
+
* @param {unknown} value
|
|
276
|
+
* @returns {{ entries: Array<{ tree: string, start_ref: string }>, dropped: number } | null}
|
|
277
|
+
* null when `value` is not an array at all.
|
|
278
|
+
*/
|
|
279
|
+
function sanitizeWorkTrees(value) {
|
|
280
|
+
if (!Array.isArray(value)) return null;
|
|
281
|
+
const entries = [];
|
|
282
|
+
for (const item of value) {
|
|
283
|
+
if (item && typeof item.tree === 'string' && isAbsolute(item.tree)
|
|
284
|
+
&& typeof item.start_ref === 'string' && FULL_SHA_RE.test(item.start_ref)) {
|
|
285
|
+
entries.push({ tree: item.tree, start_ref: item.start_ref });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return { entries, dropped: value.length - entries.length };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The own checkout's top level — the tree emit-session runs in (process.cwd(),
|
|
293
|
+
* where it also reads STATE.md and current-session.json). null outside a git
|
|
294
|
+
* repo. Never throws.
|
|
295
|
+
*
|
|
296
|
+
* @returns {string|null}
|
|
297
|
+
*/
|
|
298
|
+
function ownCheckoutTop() {
|
|
299
|
+
try {
|
|
300
|
+
const top = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
301
|
+
encoding: 'utf8',
|
|
302
|
+
timeout: 10_000,
|
|
303
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
304
|
+
}).trim();
|
|
305
|
+
return top ? realOrResolved(top) : null;
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
208
308
|
}
|
|
209
|
-
return null;
|
|
210
309
|
}
|
|
211
310
|
|
|
212
311
|
function parseArgs(argv) {
|
|
213
|
-
|
|
312
|
+
// The default shares the events ledger's main-checkout root (#1584).
|
|
313
|
+
// Explicit --file paths below remain verbatim, including relative paths.
|
|
314
|
+
// #1659: a linked worktree whose common dir is not named `.git`
|
|
315
|
+
// (`--separate-git-dir`, bare repo) has no main checkout to map to, and the
|
|
316
|
+
// resolver falls back to the WORKTREE — a second, per-worktree ledger. The
|
|
317
|
+
// default is therefore resolved after parsing and refused when unmapped.
|
|
318
|
+
const args = {
|
|
319
|
+
file: null,
|
|
320
|
+
// true when --file was not given and the default sessions ledger is used
|
|
321
|
+
fileDefaulted: false,
|
|
322
|
+
entry: null,
|
|
323
|
+
sessionUuid: null,
|
|
324
|
+
};
|
|
214
325
|
for (let i = 0; i < argv.length; i++) {
|
|
215
326
|
const a = argv[i];
|
|
216
327
|
if (a === '--file') args.file = argv[++i];
|
|
@@ -219,7 +330,7 @@ function parseArgs(argv) {
|
|
|
219
330
|
else if (a === '--help' || a === '-h') {
|
|
220
331
|
process.stdout.write(
|
|
221
332
|
'Usage: node scripts/emit-session.mjs [--file PATH] [--entry JSON]\n' +
|
|
222
|
-
' --file target JSONL file (default:
|
|
333
|
+
' --file explicit target JSONL file (default: main-checkout/.orchestrator/metrics/sessions.jsonl)\n' +
|
|
223
334
|
' --entry entry JSON (if omitted, read from stdin)\n' +
|
|
224
335
|
' --session-uuid raw harness UUID (default: owned .orchestrator/current-session.json)\n' +
|
|
225
336
|
'Exit codes: 0 append ok, 1 validation error, 2 I/O error\n'
|
|
@@ -230,6 +341,22 @@ function parseArgs(argv) {
|
|
|
230
341
|
process.exit(2);
|
|
231
342
|
}
|
|
232
343
|
}
|
|
344
|
+
if (args.file === null) {
|
|
345
|
+
// The cwd is the EXPLICIT root (like the former resolveEventsLedgerRoot
|
|
346
|
+
// call and every other sessions.jsonl reader), so a launch-dir env var
|
|
347
|
+
// never redirects the default --file; an unmapped worktree is still
|
|
348
|
+
// detected (#1659).
|
|
349
|
+
const ledger = explainEventsLedgerRoot(process.cwd());
|
|
350
|
+
if (!ledger.resolved) {
|
|
351
|
+
process.stderr.write(
|
|
352
|
+
`emit-session: refusing the default --file: ${ledger.reason}; pass an explicit --file\n`
|
|
353
|
+
);
|
|
354
|
+
process.exit(2);
|
|
355
|
+
}
|
|
356
|
+
// ledger-root: ledger.root comes from explainEventsLedgerRoot (sessions rule)
|
|
357
|
+
args.file = join(ledger.root, '.orchestrator', 'metrics', 'sessions.jsonl');
|
|
358
|
+
args.fileDefaulted = true;
|
|
359
|
+
}
|
|
233
360
|
return args;
|
|
234
361
|
}
|
|
235
362
|
|
|
@@ -416,8 +543,15 @@ async function main() {
|
|
|
416
543
|
if (needsProfile || needsStartRef) {
|
|
417
544
|
let stateMdContents = '';
|
|
418
545
|
try {
|
|
419
|
-
|
|
420
|
-
|
|
546
|
+
// #1622: the write side's ownership rule — another platform's STATE.md
|
|
547
|
+
// not proven to be this process's counts as no STATE.md.
|
|
548
|
+
const { path: stateMdPath, rejected } = resolveStateMdWriteTarget(process.cwd());
|
|
549
|
+
if (rejected) {
|
|
550
|
+
warnForeignStateMd('emit-session', rejected, stateMdPath);
|
|
551
|
+
stateMdStartRefNote = "STATE.md foreign (another platform's, not this process's session-id)";
|
|
552
|
+
} else if (existsSync(stateMdPath)) {
|
|
553
|
+
stateMdContents = readFileSync(stateMdPath, 'utf8');
|
|
554
|
+
}
|
|
421
555
|
} catch (err) {
|
|
422
556
|
process.stderr.write(
|
|
423
557
|
`emit-session: WARN could not read STATE.md for session_profile/session_start_ref ` +
|
|
@@ -483,10 +617,28 @@ async function main() {
|
|
|
483
617
|
// with a WARN whether or not an own UUID was available (#1457 point 2) —
|
|
484
618
|
// without one the fallback cannot run, which is exactly when silence hid it.
|
|
485
619
|
if (!hasOwn('session_start_ref')) {
|
|
486
|
-
const headSha = ownUuid !== null
|
|
620
|
+
const { headSha, behind } = ownUuid !== null
|
|
487
621
|
? readOwnStartHeadSha(siblingEventsFile(args.file), ownUuid)
|
|
488
|
-
: null;
|
|
489
|
-
if (headSha !== null) {
|
|
622
|
+
: { headSha: null, behind: null };
|
|
623
|
+
if (headSha !== null && behind !== null && behind > 0) {
|
|
624
|
+
// #1668 — the checkout was provably behind its upstream at start. A pull
|
|
625
|
+
// during the session makes `<head_sha>..HEAD` span the upstream commits it
|
|
626
|
+
// pulled in; no sha known at start fixes that (merge-base(head_sha,
|
|
627
|
+
// upstream) IS head_sha when merely behind, and upstream_sha is wrong when
|
|
628
|
+
// nothing was pulled), so the ref is dropped rather than overstated.
|
|
629
|
+
// behind 0 or not measured (absent) keep the pre-#1668 adoption below.
|
|
630
|
+
process.stderr.write(
|
|
631
|
+
`emit-session: WARN no session_start_ref — ${stateMdStartRefNote}, and the own ` +
|
|
632
|
+
`session.started head_sha=${headSha} was ${behind} commit(s) behind its upstream; ` +
|
|
633
|
+
'a range from it can include pulled-in upstream commits (#1668)\n'
|
|
634
|
+
);
|
|
635
|
+
} else if (headSha !== null && startTreeDiverged(ownUuid)) {
|
|
636
|
+
process.stderr.write(
|
|
637
|
+
`emit-session: WARN no session_start_ref — ${stateMdStartRefNote}, and the own ` +
|
|
638
|
+
`session.started head_sha=${headSha} was measured in session_root, not the work_root ` +
|
|
639
|
+
'this session works in (#1652)\n'
|
|
640
|
+
);
|
|
641
|
+
} else if (headSha !== null) {
|
|
490
642
|
repaired = { ...repaired, session_start_ref: headSha };
|
|
491
643
|
process.stderr.write(
|
|
492
644
|
`emit-session: derived session_start_ref=${headSha} from the own orchestrator.session.started event\n`
|
|
@@ -503,6 +655,61 @@ async function main() {
|
|
|
503
655
|
repaired = { ...repaired, raw_session_id: ownUuid };
|
|
504
656
|
}
|
|
505
657
|
|
|
658
|
+
// `session_work_trees` (#1659) — the (tree, start_ref) pairs the staging
|
|
659
|
+
// fence recorded before each of THIS session's commits
|
|
660
|
+
// (`orchestrator.session.work_tree`, joined on the own raw id — never on a
|
|
661
|
+
// repo-global file). `session_start_ref` names one sha in the own checkout,
|
|
662
|
+
// so the field is written ONLY when it adds something: ≥2 trees, or one tree
|
|
663
|
+
// that is not the own checkout. An explicit key on the record wins, but its
|
|
664
|
+
// malformed entries are dropped with a WARN (like session_start_ref above —
|
|
665
|
+
// enforced here, not in validateSession()). Best-effort: any failure omits
|
|
666
|
+
// the field with a WARN; it never fails the emit.
|
|
667
|
+
if (hasOwn('session_work_trees')) {
|
|
668
|
+
const clean = sanitizeWorkTrees(repaired.session_work_trees);
|
|
669
|
+
if (clean === null) {
|
|
670
|
+
process.stderr.write('emit-session: WARN session_work_trees is not an array; dropping it\n');
|
|
671
|
+
const { session_work_trees: _dropped, ...rest } = repaired;
|
|
672
|
+
repaired = rest;
|
|
673
|
+
} else if (clean.dropped > 0) {
|
|
674
|
+
process.stderr.write(
|
|
675
|
+
`emit-session: WARN session_work_trees: dropping ${clean.dropped} malformed entry/entries\n`
|
|
676
|
+
);
|
|
677
|
+
repaired = { ...repaired, session_work_trees: clean.entries };
|
|
678
|
+
}
|
|
679
|
+
} else if (ownUuid !== null) {
|
|
680
|
+
try {
|
|
681
|
+
const read = readSessionWorkTrees({ sessionId: ownUuid, eventsFile: siblingEventsFile(args.file) });
|
|
682
|
+
if (read.error) {
|
|
683
|
+
process.stderr.write(
|
|
684
|
+
`emit-session: WARN could not read work_tree events (${read.error}); omitting session_work_trees\n`
|
|
685
|
+
);
|
|
686
|
+
} else if (read.malformed > 0) {
|
|
687
|
+
// Unreadable lines may hide own work_tree events — warn even when no
|
|
688
|
+
// tree was read, since then the omission itself may be the gap (#1659).
|
|
689
|
+
process.stderr.write(
|
|
690
|
+
`emit-session: WARN ${read.malformed} unreadable events ledger line(s); session_work_trees may be incomplete\n`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
if (!read.error && read.trees.length > 0) {
|
|
694
|
+
if (read.invalid > 0) {
|
|
695
|
+
process.stderr.write(
|
|
696
|
+
`emit-session: WARN ignored ${read.invalid} own work_tree event(s) without a valid tree/start_ref\n`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
// A single tree is only worth recording when it provably differs from
|
|
700
|
+
// the own checkout; outside a git repo that is unprovable → omitted.
|
|
701
|
+
const ownTop = read.trees.length >= 2 ? null : ownCheckoutTop();
|
|
702
|
+
const addsInfo = read.trees.length >= 2
|
|
703
|
+
|| (ownTop !== null && realOrResolved(read.trees[0].tree) !== ownTop);
|
|
704
|
+
if (addsInfo) repaired = { ...repaired, session_work_trees: read.trees };
|
|
705
|
+
}
|
|
706
|
+
} catch (err) {
|
|
707
|
+
process.stderr.write(
|
|
708
|
+
`emit-session: WARN session_work_trees derivation failed (${err?.message ?? err}); omitting it\n`
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
506
713
|
// Token rollup (#1436) — mechanical, replacing the session-metrics-write.md
|
|
507
714
|
// prose step that had no production caller. Joins the sibling subagents.jsonl
|
|
508
715
|
// on the own raw UUID and fills ONLY keys the entry does not carry; an
|
|
@@ -518,10 +725,22 @@ async function main() {
|
|
|
518
725
|
if (ownUuid !== null && !hasOwn('total_tokens')) {
|
|
519
726
|
let rollup = null;
|
|
520
727
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
728
|
+
// A defaulted --file reads the subagents ledger where the hook writer
|
|
729
|
+
// (hooks/subagent-telemetry.mjs) puts it: resolved from the cwd first, then
|
|
730
|
+
// from the ledger root — the hook resolves from the LAUNCH dir, which is
|
|
731
|
+
// the main checkout when the session entered this worktree later and the
|
|
732
|
+
// repo versions the ledger. First copy with a record of this session wins,
|
|
733
|
+
// so nothing is counted twice. An explicit --file keeps its sibling.
|
|
734
|
+
const candidates = args.fileDefaulted
|
|
735
|
+
? [...new Set([
|
|
736
|
+
resolveLedgerPath('.orchestrator/metrics/subagents.jsonl', process.cwd()),
|
|
737
|
+
resolveLedgerPath('.orchestrator/metrics/subagents.jsonl', resolveEventsLedgerRoot(process.cwd())),
|
|
738
|
+
])]
|
|
739
|
+
: [join(dirname(args.file), 'subagents.jsonl')];
|
|
740
|
+
for (const subagentsPath of candidates) {
|
|
741
|
+
rollup = rollupSessionTokens({ parentSessionId: ownUuid, subagentsPath });
|
|
742
|
+
if (rollup.matched_records > 0) break;
|
|
743
|
+
}
|
|
525
744
|
} catch (err) {
|
|
526
745
|
process.stderr.write(
|
|
527
746
|
`emit-session: WARN token rollup failed (${err?.message ?? err}); omitting token fields\n`
|
package/scripts/eval-session.mjs
CHANGED
|
@@ -74,6 +74,7 @@ import { appendEvalRecord, readEvalRecords } from './lib/eval/sink.mjs';
|
|
|
74
74
|
import { writeStdoutLineSync } from './lib/io.mjs';
|
|
75
75
|
import { VALID_MODEL_SOURCES } from './lib/eval/schema.mjs';
|
|
76
76
|
import { SessionResolutionError } from './lib/eval/session-resolve.mjs';
|
|
77
|
+
import { resolveLedgerPath } from './lib/ledger-path.mjs';
|
|
77
78
|
|
|
78
79
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
79
80
|
|
|
@@ -103,7 +104,8 @@ OPTIONS
|
|
|
103
104
|
--session <id> session_id to evaluate (default: last completed session)
|
|
104
105
|
--json emit the record (or verify result) as JSON on stdout
|
|
105
106
|
--no-write evaluate without appending to the eval journal
|
|
106
|
-
--metrics-dir <path> metrics directory (default: .orchestrator/metrics
|
|
107
|
+
--metrics-dir <path> metrics directory (default: .orchestrator/metrics of this repo;
|
|
108
|
+
in a linked worktree the main checkout's when it is gitignored)
|
|
107
109
|
--rubric <path> rubric file to hash for provenance (default: ${DEFAULT_RUBRIC_PATH})
|
|
108
110
|
--model-id <id> evaluated model id (default: $ANTHROPIC_MODEL or "unknown")
|
|
109
111
|
--model-source <src> ${VALID_MODEL_SOURCES.join(' | ')} (default: self-report)
|
|
@@ -159,8 +161,20 @@ function renderHuman(record, summary, writeResult) {
|
|
|
159
161
|
return lines.join('\n');
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
/**
|
|
165
|
+
* The metrics directory: `--metrics-dir` as given, else this repo's
|
|
166
|
+
* `.orchestrator/metrics` — from a linked worktree the main checkout's when
|
|
167
|
+
* `eval.jsonl` is gitignored there, so the record survives the worktree (#1722).
|
|
168
|
+
* The engine reads `sessions.jsonl` beside it through the same main-checkout
|
|
169
|
+
* mapping (engine.mjs, #1592).
|
|
170
|
+
*/
|
|
171
|
+
function resolveMetricsDir(flag) {
|
|
172
|
+
if (flag) return path.resolve(flag);
|
|
173
|
+
return path.dirname(resolveLedgerPath('.orchestrator/metrics/eval.jsonl', process.cwd()));
|
|
174
|
+
}
|
|
175
|
+
|
|
162
176
|
function runEvaluate(values) {
|
|
163
|
-
const metricsDir = values['metrics-dir']
|
|
177
|
+
const metricsDir = resolveMetricsDir(values['metrics-dir']);
|
|
164
178
|
const rubricPath = values.rubric || DEFAULT_RUBRIC_PATH;
|
|
165
179
|
const modelSource = values['model-source'] || 'self-report';
|
|
166
180
|
if (!VALID_MODEL_SOURCES.includes(modelSource)) {
|
|
@@ -219,7 +233,7 @@ function runEvaluate(values) {
|
|
|
219
233
|
}
|
|
220
234
|
|
|
221
235
|
function runVerify(runId, values) {
|
|
222
|
-
const metricsDir = values['metrics-dir']
|
|
236
|
+
const metricsDir = resolveMetricsDir(values['metrics-dir']);
|
|
223
237
|
const rubricPath = values.rubric || DEFAULT_RUBRIC_PATH;
|
|
224
238
|
|
|
225
239
|
const records = readEvalRecords(path.join(metricsDir, 'eval.jsonl'));
|
|
@@ -60,12 +60,14 @@ import {
|
|
|
60
60
|
CURRENT_ANONYMIZATION_VERSION,
|
|
61
61
|
} from './lib/learnings.mjs';
|
|
62
62
|
import { withLearningsLock } from './lib/learnings/io.mjs';
|
|
63
|
+
import { assertStoreRoleUsable, isCanonicalStore, resolveStoreRole } from './lib/learnings/store-role.mjs';
|
|
63
64
|
import { findProjectRoot, resolveInstructionFile, expandTilde, redactHomeDir } from './lib/common.mjs';
|
|
64
65
|
import { parseSessionConfig } from './lib/config.mjs';
|
|
65
66
|
import { createSecretValueMasker } from './lib/secret-masker.mjs';
|
|
66
67
|
import { stableHostname, readHostAliases } from './lib/host-identity.mjs';
|
|
67
68
|
import { emitEvent, sessionAttribution } from './lib/events.mjs';
|
|
68
69
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
70
|
+
import { resolveLedgerPath } from './lib/ledger-path.mjs';
|
|
69
71
|
|
|
70
72
|
// Vault-relative default write target (Epic #774 — docs Public-Split removed
|
|
71
73
|
// the prior in-repo generated telemetry doc in favor of the private Meta-Vault).
|
|
@@ -572,6 +574,7 @@ function parseArgs(argv) {
|
|
|
572
574
|
const out = {
|
|
573
575
|
dryRun: false,
|
|
574
576
|
promote: false,
|
|
577
|
+
// ledger-root: parse default, routed after parsing unless --input is explicit
|
|
575
578
|
input: '.orchestrator/metrics/learnings.jsonl',
|
|
576
579
|
output: undefined, // resolved lazily below (default = vault-dir target)
|
|
577
580
|
generatedAt: new Date().toISOString(),
|
|
@@ -581,11 +584,12 @@ function parseArgs(argv) {
|
|
|
581
584
|
// themselves (see their opts.repoRoot doc for why).
|
|
582
585
|
repoRoot: findProjectRoot(),
|
|
583
586
|
};
|
|
587
|
+
let inputExplicit = false;
|
|
584
588
|
for (let i = 0; i < argv.length; i++) {
|
|
585
589
|
const a = argv[i];
|
|
586
590
|
if (a === '--dry-run') out.dryRun = true;
|
|
587
591
|
else if (a === '--promote') out.promote = true;
|
|
588
|
-
else if (a === '--input') out.input = argv[++i];
|
|
592
|
+
else if (a === '--input') { out.input = argv[++i]; inputExplicit = true; }
|
|
589
593
|
else if (a === '--output') out.output = argv[++i];
|
|
590
594
|
else if (a === '--generated-at') out.generatedAt = argv[++i];
|
|
591
595
|
else if (a === '--help' || a === '-h') {
|
|
@@ -598,7 +602,12 @@ function parseArgs(argv) {
|
|
|
598
602
|
' (backup created first). Use with --dry-run to preview.\n' +
|
|
599
603
|
' --dry-run Print markdown to stdout; do not write any files.\n' +
|
|
600
604
|
' --output FILE Override the default write target. Default: resolved from\n' +
|
|
601
|
-
' vault-integration.vault-dir (see script header doc).\n'
|
|
605
|
+
' vault-integration.vault-dir (see script header doc).\n' +
|
|
606
|
+
'\n' +
|
|
607
|
+
'Exit codes: 0 success, 1 export failed (incl. --promote without --dry-run on a\n' +
|
|
608
|
+
' host whose learnings-store role is inbox or invalid, #1516:\n' +
|
|
609
|
+
' nothing written),\n' +
|
|
610
|
+
' 2 unknown argument.\n'
|
|
602
611
|
);
|
|
603
612
|
process.exit(0);
|
|
604
613
|
} else {
|
|
@@ -619,6 +628,13 @@ function parseArgs(argv) {
|
|
|
619
628
|
}
|
|
620
629
|
out.output = resolved;
|
|
621
630
|
}
|
|
631
|
+
// #1722: the DEFAULT input routes through the ledger resolver (main checkout
|
|
632
|
+
// when cwd is a linked worktree that gitignores it); outside one the relative
|
|
633
|
+
// default is kept verbatim. An explicit --input stays as given.
|
|
634
|
+
if (!inputExplicit) {
|
|
635
|
+
const routed = resolveLedgerPath(out.input, process.cwd());
|
|
636
|
+
if (routed !== path.resolve(out.input)) out.input = routed;
|
|
637
|
+
}
|
|
622
638
|
return out;
|
|
623
639
|
}
|
|
624
640
|
|
|
@@ -650,6 +666,17 @@ export async function promoteHwLearnings(opts) {
|
|
|
650
666
|
// nothing and takes no lock. A lock not acquired throws LearningsLockError
|
|
651
667
|
// (nothing written), which the CLI reports as `export failed: …`, exit 1.
|
|
652
668
|
if (opts.dryRun) return promoteHwLearningsUnlocked(opts);
|
|
669
|
+
// #1516 fail-closed: an invalid store role throws LearningsStoreRoleError
|
|
670
|
+
// (CLI: `export failed: …`, exit 1) before anything is read, locked or emitted.
|
|
671
|
+
assertStoreRoleUsable(opts.input);
|
|
672
|
+
// #1516: on an inbox-role host the live store is never rewritten, and a
|
|
673
|
+
// promoted twin keeps its private original's id, so the inbox rewrite would
|
|
674
|
+
// drop it silently. Refuse before anything is read, locked or emitted.
|
|
675
|
+
if (isCanonicalStore(opts.input) && resolveStoreRole().role === 'inbox') {
|
|
676
|
+
throw new Error(
|
|
677
|
+
'store-role inbox: --promote rewrites learnings.jsonl, which this host never does — run it on the writer host'
|
|
678
|
+
);
|
|
679
|
+
}
|
|
653
680
|
return withLearningsLock(opts.input, () => promoteHwLearningsUnlocked(opts));
|
|
654
681
|
}
|
|
655
682
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Read-only CLI for explicitly supplied, stable snapshot copies (#1462). */
|
|
3
|
-
import {
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { TextDecoder } from 'node:util';
|
|
6
6
|
import { auditCheckins } from './lib/fleet-checkin-audit.mjs';
|
|
7
7
|
import { isMainModule } from './lib/is-main-module.mjs';
|
|
8
|
+
import { openNoFollow } from './lib/no-follow-open.mjs';
|
|
8
9
|
|
|
9
10
|
const HELP = `Usage: fleet-checkin-audit --input-root DIR --registry FILE --checkins FILE --repo KEY [--repo KEY] --at UTC [--json]
|
|
10
11
|
All five input flags are required. --repo may repeat with distinct explicit keys.
|
|
@@ -80,10 +81,13 @@ async function readSnapshot({ target, stat }) {
|
|
|
80
81
|
// The parent check above and this open are not atomic: a same-user process can swap a parent
|
|
81
82
|
// directory in between. Fine while the operator names the snapshot files and the result grants
|
|
82
83
|
// nothing; revisit (#1549) once paths come from a foreign owner or the result gates a decision.
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
let handle;
|
|
85
|
+
try { handle = await openNoFollow(target); }
|
|
86
|
+
catch (error) {
|
|
87
|
+
// inspect() already proved a regular non-symlink file: any of these means it changed since.
|
|
88
|
+
if (['ERR_FILE_CHANGED', 'ELOOP', 'ERR_NOT_REGULAR_FILE'].includes(error?.code)) throw failure('changed-input-file', 1);
|
|
89
|
+
throw error;
|
|
85
90
|
}
|
|
86
|
-
const handle = await fs.open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
87
91
|
let bytes;
|
|
88
92
|
try {
|
|
89
93
|
const actual = await handle.stat();
|