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
|
@@ -48,10 +48,14 @@ For session-end specifically: the preamble is DETECTION-ONLY. The lock-release p
|
|
|
48
48
|
|
|
49
49
|
```javascript
|
|
50
50
|
try {
|
|
51
|
+
const { pathToFileURL } = await import('node:url');
|
|
51
52
|
const { appendSkillInvocation, DEFAULT_SKILL_INVOCATIONS_PATH } =
|
|
52
|
-
await import('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs');
|
|
53
|
-
const
|
|
54
|
-
|
|
53
|
+
await import(pathToFileURL('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs').href);
|
|
54
|
+
const { resolveLedgerPath } =
|
|
55
|
+
await import(pathToFileURL('${PLUGIN_ROOT}/scripts/lib/ledger-path.mjs').href);
|
|
56
|
+
// In a linked worktree the main checkout's ledger when it is gitignored there,
|
|
57
|
+
// so the record survives the worktree (#1722).
|
|
58
|
+
await appendSkillInvocation(resolveLedgerPath(DEFAULT_SKILL_INVOCATIONS_PATH, process.cwd()), {
|
|
55
59
|
timestamp: new Date().toISOString(),
|
|
56
60
|
event: 'selected',
|
|
57
61
|
skill: 'session-orchestrator:session-end',
|
|
@@ -63,7 +67,7 @@ try {
|
|
|
63
67
|
|
|
64
68
|
## Phase 1: Plan Verification
|
|
65
69
|
|
|
66
|
-
> Always runs, first. Reads back the agreed plan and dispositions every item: 1.1 Done (verify with evidence) + 1.1a File-Level Grounding, 1.2 / 1.3 / 1.4 which COLLECT carryover candidates and file nothing (#769), 1.5 Discovery Scan, 1.6 Safety Review + 1.6.6 "What Not To Retry", then **1.65 Handover Alignment Gate** — the only place `[Carryover]` filing is authorized to originate, fail-open to "everything carries" — followed by 1.7 Metrics Collection, 1.8 Session Review, and 1.9 / 1.10 Mission-Status classification + breakdown. Full procedure: [`plan-verification.md`](plan-verification.md).
|
|
70
|
+
> Always runs, first. Reads back the agreed plan and dispositions every item: 1.1 Done (verify with evidence) + 1.1a File-Level Grounding + 1.1b External Acceptance (open `[Abnahme]` issues, closed only on oracle evidence), 1.2 / 1.3 / 1.4 which COLLECT carryover candidates and file nothing (#769), 1.5 Discovery Scan, 1.6 Safety Review + 1.6.6 "What Not To Retry", then **1.65 Handover Alignment Gate** — the only place `[Carryover]` filing is authorized to originate, fail-open to "everything carries" — followed by 1.7 Metrics Collection, 1.8 Session Review, and 1.9 / 1.10 Mission-Status classification + breakdown. Full procedure: [`plan-verification.md`](plan-verification.md).
|
|
67
71
|
|
|
68
72
|
## Phase 2: Quality Gate
|
|
69
73
|
|
|
@@ -159,7 +163,9 @@ node scripts/release-session-lock.mjs --session-id "<raw session_id>" --json
|
|
|
159
163
|
|
|
160
164
|
`--session-id` is the PHYSICAL raw value established by session-start Phase 1.2 and stored in `.orchestrator/session.lock` `session_id` — never STATE.md `session:` and never `semantic_session_id`, both of which are attribution labels.
|
|
161
165
|
|
|
162
|
-
|
|
166
|
+
No `--repo-root`: the command looks for the checkout whose lock carries that id itself (#1730) — the registry's `session_root`, the cwd's checkout, its main checkout, a registered linked worktree — and names it in the JSON (`repo_root`, `root_source`). Before, it read only the cwd, answered `absent` with exit 0 when the lock sat in the other checkout, and the lock stayed.
|
|
167
|
+
|
|
168
|
+
The command releases the lock only when that raw id owns it, emits the terminal `orchestrator.session.lock.released` breadcrumb, and then RE-READS the lock path: **exit 0 means the lock is provably gone.** It replaces the hand-executed `release()` this phase used to prescribe, which deleted the lock silently — the SessionEnd hook then found `status: 'absent'` and emitted nothing either, so a lock lifecycle ended with no terminal event at all (#1395: 5 `lock.acquired` against 0 `lock.released` in this repo). `outcome: "absent"` is a normal exit-0 result (idempotent re-run; nothing was released, so no event is written). A verified release also leaves `.orchestrator/runtime/lock-released/<raw session_id>.json` (`release_marker: true` in the JSON), so a later `compact` or `resume` of the same raw session does not re-create the lock (#1651); `startup` and `clear` take a lock as before, and the session's next `acquire()` removes the marker.
|
|
163
169
|
|
|
164
170
|
A non-zero exit is a WARN, never a blocker — log `⚠ session-lock: <outcome>` and continue; the TTL provides automatic expiry for the next session:
|
|
165
171
|
|
|
@@ -187,7 +193,7 @@ Use Conventional Commits format:
|
|
|
187
193
|
type(scope): description
|
|
188
194
|
|
|
189
195
|
- [bullet points of what changed]
|
|
190
|
-
- Closes #IID1, #IID2 (if applicable)
|
|
196
|
+
- Closes #IID1, #IID2 (if applicable — never an `[Abnahme]` IID, plan-verification 1.1b)
|
|
191
197
|
|
|
192
198
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
193
199
|
```
|
|
@@ -218,7 +224,37 @@ between the markers and executes it, so no second copy of this command may exist
|
|
|
218
224
|
# so a consumer repo (even one without a Session Config) skips with exit 0.
|
|
219
225
|
# With a remote, Session Config `mirror` decides (#1034), read through parse-config.mjs:
|
|
220
226
|
# `mirror: none` → never push, even with a 'github' remote (explicit opt-out)
|
|
221
|
-
# `mirror: github` or key absent (`mirror: null` counts as absent) → push
|
|
227
|
+
# `mirror: github` or key absent (`mirror: null` counts as absent) → push HEAD,
|
|
228
|
+
# but only after `github-export.mjs --plain-guard` (#1645):
|
|
229
|
+
# when HEAD or origin's main (ls-remote, fetched if not
|
|
230
|
+
# local) carries .orchestrator/policy/public-export.json,
|
|
231
|
+
# the repo publishes curated only — a checkout whose
|
|
232
|
+
# CLAUDE.md predates `mirror: curated` must not push its
|
|
233
|
+
# full private history → WARN, exit 1, nothing pushed.
|
|
234
|
+
# origin unreadable → same refusal (fail closed); no
|
|
235
|
+
# 'origin' remote → refused unless `mirror: github` is
|
|
236
|
+
# set explicitly (nothing to check the policy against).
|
|
237
|
+
# `mirror: curated` → publish ONE squash snapshot (scripts/github-export.mjs,
|
|
238
|
+
# #1631) on top of the verified public github main,
|
|
239
|
+
# never HEAD. Source anchor: only when HEAD is on branch
|
|
240
|
+
# `main` AND equals origin's main (`git ls-remote`, after
|
|
241
|
+
# the 4.3 push) — GitHub main deploys production, so a
|
|
242
|
+
# feature branch or unpushed/unreviewed local commits
|
|
243
|
+
# skip (exit 0, nothing pushed); origin unreadable →
|
|
244
|
+
# WARN, exit 1. Export exit 3 = nothing to publish (no
|
|
245
|
+
# push), other non-zero → WARN, exit 1. Bound to the
|
|
246
|
+
# scanned sha (read from the export's stdout only):
|
|
247
|
+
# push URL must equal fetch URL, then `--verify <sha>`
|
|
248
|
+
# re-checks it right before the push (one commit on the
|
|
249
|
+
# ls-remote-verified github main, trailer
|
|
250
|
+
# `Source-Commit: <origin main>`, no excluded path in its
|
|
251
|
+
# tree), else WARN, exit 1; the push sends that sha, not
|
|
252
|
+
# the export ref. Plain push, no lease: the commit's
|
|
253
|
+
# parent IS the verified sha, and a plain push is
|
|
254
|
+
# fast-forward-only, so GitHub itself rejects it if its
|
|
255
|
+
# main moved to anything that is not an ancestor of that
|
|
256
|
+
# sha — the same anchor a lease would enforce, without a
|
|
257
|
+
# force flag (blocked by the destructive-command guard).
|
|
222
258
|
# any other value / config unreadable → loud WARN, no push, exit 1
|
|
223
259
|
# The plugin root is resolved, not assumed: a fresh /close shell often lacks
|
|
224
260
|
# PLUGIN_ROOT. Order per skills/_shared/config-reading.md (native PLUGIN_ROOT,
|
|
@@ -260,8 +296,49 @@ elif ! mirror_cfg=$(printf '%s' "$cfg_json" | node -e 'let s="";process.stdin.on
|
|
|
260
296
|
exit 1
|
|
261
297
|
elif [ "$mirror_cfg" = "none" ]; then
|
|
262
298
|
echo "GitHub mirror: 'mirror: none' in Session Config — skipping (not an error)."
|
|
263
|
-
elif [ -n "$mirror_cfg" ] && [ "$mirror_cfg" != "github" ]; then
|
|
264
|
-
echo "WARN GitHub mirror: unknown 'mirror: ${mirror_cfg}' (expected none | github) — nothing pushed." >&2
|
|
299
|
+
elif [ -n "$mirror_cfg" ] && [ "$mirror_cfg" != "github" ] && [ "$mirror_cfg" != "curated" ]; then
|
|
300
|
+
echo "WARN GitHub mirror: unknown 'mirror: ${mirror_cfg}' (expected none | github | curated) — nothing pushed." >&2
|
|
301
|
+
exit 1
|
|
302
|
+
elif [ "$mirror_cfg" = "curated" ]; then
|
|
303
|
+
if ! origin_ls=$(git ls-remote origin refs/heads/main 2>&1); then
|
|
304
|
+
echo "WARN GitHub mirror: cannot read origin's main — curated source not verifiable, nothing pushed." >&2
|
|
305
|
+
echo " git said: ${origin_ls}" >&2
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
origin_main=$(printf '%s\n' "$origin_ls" | awk '$2 == "refs/heads/main" { print $1 }')
|
|
309
|
+
if [ "$(git symbolic-ref --short -q HEAD)" != "main" ] || [ -z "$origin_main" ] || [ "$(git rev-parse HEAD)" != "$origin_main" ]; then
|
|
310
|
+
echo "GitHub mirror: 'mirror: curated' publishes only a HEAD on main that equals origin's main (HEAD $(git symbolic-ref --short -q HEAD || echo detached) $(git rev-parse --short HEAD), origin main ${origin_main:-none}) — skipping (not an error)."
|
|
311
|
+
else
|
|
312
|
+
# stdout carries ONLY the sha (stderr goes straight to the operator), so the
|
|
313
|
+
# sha pushed below is the one the export scanned — never a ref name that could
|
|
314
|
+
# be moved in between.
|
|
315
|
+
export_sha=$(GIT_NO_REPLACE_OBJECTS=1 node "$so_root/scripts/github-export.mjs" --source "$origin_main")
|
|
316
|
+
export_rc=$?
|
|
317
|
+
if [ "$export_rc" -eq 3 ]; then
|
|
318
|
+
echo "GitHub mirror: curated snapshot of ${origin_main} unchanged — nothing to publish."
|
|
319
|
+
elif [ "$export_rc" -ne 0 ]; then
|
|
320
|
+
echo "WARN GitHub mirror: curated export REFUSED (exit ${export_rc}, reason above) — nothing pushed." >&2
|
|
321
|
+
exit 1
|
|
322
|
+
elif ! printf '%s' "$export_sha" | grep -Eqx '[0-9a-f]{40}([0-9a-f]{24})?'; then
|
|
323
|
+
echo "WARN GitHub mirror: the export printed no commit sha — nothing pushed." >&2
|
|
324
|
+
exit 1
|
|
325
|
+
elif [ "$(git remote get-url github)" != "$(git remote get-url --push github)" ]; then
|
|
326
|
+
echo "WARN GitHub mirror: the push URL of 'github' differs from its fetch URL (pushurl or pushInsteadOf) — the verified main would not describe the push target; nothing pushed." >&2
|
|
327
|
+
exit 1
|
|
328
|
+
elif ! GIT_NO_REPLACE_OBJECTS=1 node "$so_root/scripts/github-export.mjs" --verify "$export_sha" --source "$origin_main"; then
|
|
329
|
+
echo "WARN GitHub mirror: ${export_sha} is not one clean snapshot of ${origin_main} on the verified github main — nothing pushed." >&2
|
|
330
|
+
exit 1
|
|
331
|
+
elif push_out=$(git push github "${export_sha}:refs/heads/main" 2>&1); then
|
|
332
|
+
echo "GitHub mirror: pushed curated snapshot ${export_sha} (source ${origin_main}) -> ${mirror_url}"
|
|
333
|
+
else
|
|
334
|
+
echo "WARN GitHub mirror PUSH FAILED: curated snapshot ${export_sha} is NOT on ${mirror_url}" >&2
|
|
335
|
+
echo "${push_out}" >&2
|
|
336
|
+
echo "WARN Mirror is stale — anything wired to it (site deploy) will not update." >&2
|
|
337
|
+
exit 1
|
|
338
|
+
fi
|
|
339
|
+
fi
|
|
340
|
+
elif ! GIT_NO_REPLACE_OBJECTS=1 node "$so_root/scripts/github-export.mjs" --plain-guard --mirror-key "${mirror_cfg:-unset}"; then
|
|
341
|
+
echo "WARN GitHub mirror: plain push of HEAD REFUSED (reason above) — this repo publishes curated only; nothing pushed." >&2
|
|
265
342
|
exit 1
|
|
266
343
|
elif push_out=$(git push github HEAD 2>&1); then
|
|
267
344
|
echo "GitHub mirror: pushed $(git rev-parse HEAD) -> ${mirror_url}"
|
|
@@ -278,6 +355,12 @@ State 3 exits non-zero on purpose: it is the only machine-readable signal that t
|
|
|
278
355
|
behind. Report it to the operator in the session summary; do not retry silently and do not
|
|
279
356
|
swallow it with `|| true`.
|
|
280
357
|
|
|
358
|
+
### 4.5 Only a Pipeline or Release Train Left
|
|
359
|
+
|
|
360
|
+
Do not wait for a pipeline or release train inside session-end. If the session has no other work left, apply `skills/_shared/fleet-protocol.md` § Rücklauf an den Navigator (Pflicht) with the wait case `zug-<name>`: exactly one report in this `/close` turn, only with fleet integration (`flotte: true`). That the waiting answer before `/close` already reported the train is intended — every waiting turn triggered by an input (`/close` is one) reports once (#1611); turns woken by `/loop`, a Monitor event, an own background agent or shell, or ScheduleWakeup do not. Write NO handover or train manifest here — a coordinator only acts on one written with the waiting answer before `/close` (same section, Übergabe und Zug-Manifest) — and never invoke `/close` or another close yourself. Then continue with Phase 4a; never gate the close on a reply (CSM-004).
|
|
361
|
+
|
|
362
|
+
**Queue marks on the own handover (#1639).** With fleet integration — whether or not a train is left — re-check the newest own handover or train manifest in the fleet folder (fleet-protocol § Flotten-Ordner und Session-Name gives `<name>`, `<cwd>` and `<startedAt>`): `node "$PLUGIN_ROOT/scripts/handover-queue-check.mjs" --name <name> --fleet-dir <cwd>/.orchestrator/runtime/fleet --since <startedAt> --apply --repo <repo> [--navigator-cli <koordinator-cli>]`, same `<repo>` and CLI as fleet-protocol Schritt 1a. It files every open point that still lacks a `queue:#`/`issue:#` mark and writes the id into the file; this edits the existing file and is not the forbidden new handover. `--since` keeps a predecessor's file of the same session name out (names repeat across days). Exit 3 (points left unmarked, e.g. the CLI is unreachable): name `Übergabe ohne Queue-IDs: <n> Punkte` with the listed points in the session summary. It never blocks the close; without fleet integration or without an own file it is a silent no-op (exit 0).
|
|
363
|
+
|
|
281
364
|
## Phase 4a: Auto-Promoted Worktree Cleanup (#575 P3.2)
|
|
282
365
|
|
|
283
366
|
> Skip when `persistence: false`; skip silently unless the CURRENT worktree is an auto-promoted sibling — `detectAutoPromotedWorktree()` from `scripts/lib/session-end/worktree-cleanup.mjs` (marker `.orchestrator/promoted-from.json` first, legacy basename match as fallback, #1069). Runs AFTER the Phase 4 commit+push, never before (#490 durableCommit ordering). Clean worktree → auto-remove with WARN; dirty → the 3-option AUQ (`Behalten` / `Löschen` / `Manuell`) before any destructive action, per PSA-003. Full procedure: [`references/phase-4a-worktree-cleanup.md`](references/phase-4a-worktree-cleanup.md).
|
|
@@ -292,7 +375,7 @@ swallow it with `|| true`.
|
|
|
292
375
|
|
|
293
376
|
## Phase 6: Final Report
|
|
294
377
|
|
|
295
|
-
Present to the user the **Session Summary**: Completed / Carried Over / Dropped at Handover Gate (#769) / New Issues Created / Unresolved Review Findings (MED-LOW, #617) / Metrics — including the Docs Health line rendered from the Phase 2.3 result and the Custom Phases line from Phase 2.5 — / Next Session Recommendations. Full template plus the Test-delta and Documentation-Coverage anchors: [`references/session-summary-template.md`](references/session-summary-template.md).
|
|
378
|
+
Present to the user the **Session Summary**: Completed / Abnahme offen (open `[Abnahme]` issues from Phase 1.1b — never counted as Completed, #1678) / Carried Over / Dropped at Handover Gate (#769) / New Issues Created / Unresolved Review Findings (MED-LOW, #617) / Metrics — including the Docs Health line rendered from the Phase 2.3 result and the Custom Phases line from Phase 2.5 — / Next Session Recommendations. Full template plus the Test-delta and Documentation-Coverage anchors: [`references/session-summary-template.md`](references/session-summary-template.md).
|
|
296
379
|
|
|
297
380
|
## Sub-File Reference
|
|
298
381
|
|
|
@@ -318,7 +401,7 @@ Present to the user the **Session Summary**: Completed / Carried Over / Dropped
|
|
|
318
401
|
| `references/phase-3-documentation-updates.md` § 3.7d | Session-Eval (opt-in — #803) — `node scripts/eval-session.mjs --json` scores the just-closed session; gated on `eval.enabled` + `eval.mode != off` (parsed by `scripts/lib/config/eval.mjs`), optional `eval-judge` dispatch + `writeEvalReport`, advisory/never-blocks-close, ordered after 3.7 (record must exist) and before 3.4/Phase 4 (record committed with the session). Full flow in `skills/eval/SKILL.md` |
|
|
319
402
|
| `references/phase-3-7e-vault-orphans.md` | Phase 3.7e Vault Orphans (#1563) — `node scripts/vault-orphans.mjs --since <started_at> --json` (`findVaultOrphans` in `scripts/lib/session-end/vault-orphans.mjs`, read-only `git ls-files --others`) names untracked vault files modified since the session start, minus `_generator: session-orchestrator-` files; candidates only (mtime is no proof), one Open Question via `appendOpenQuestionOnDisk`, never commits/moves/deletes in the vault; `unavailable` = "could not measure", never "no orphans"; gated on `vault-integration.enabled`, ordered after 3.7d and before 3.4 |
|
|
320
403
|
| (inline) Phase 3.8 | Session Lock Release — `release()` uses the physical raw `session_id`; raw mismatch/absent is non-fatal but never repaired with semantic labels or proof (live ambiguity remains for TTL/Reaper); fs-errors are non-fatal; runs after STATE.md writes and before Phase 4 commit staging |
|
|
321
|
-
| (inline) Phase 4 | Commit & Push — stage individually (PSA-004), commit, push to origin, then the `github-mirror-push` block (4 states: not-a-repo / no `github` remote / pushed / push failed) |
|
|
404
|
+
| (inline) Phase 4 | Commit & Push — stage individually (PSA-004), commit, push to origin, then the `github-mirror-push` block (4 states: not-a-repo / no `github` remote / pushed / push failed), then 4.5 (only a pipeline or train left → one fleet Rücklauf `zug-<name>` in this turn, fleet integration only; no manifest, no self-close) |
|
|
322
405
|
| `references/phase-4a-worktree-cleanup.md` | Phase 4a full procedural body — auto-promoted-worktree detection (`detectAutoPromotedWorktree`, marker-keyed since #1069), clean-check, clean auto-remove path, dirty 3-option AUQ (`Behalten`/`Löschen`/`Manuell`), PSA-003 + #490 ordering rationale |
|
|
323
406
|
| `references/phase-4b-worktree-orphan-sweep.md` | Phase 4b full procedural body — `checkWorktreeOrphans()` read-only proposal set, the coordinator-rendered AUQ, opt-in `worktree-orphans.enabled` gate |
|
|
324
407
|
| `references/phase-5-issue-cleanup.md` | Phase 5 full procedural body — close resolved issues via `node "$PLUGIN_ROOT/scripts/lib/issue-close-strip-labels.mjs" --close` (strip `status:*` → close → re-read; `closed: false` is a finding, not done, #308), Step 3 filing of the Phase 1.65 carry-list incl. the deferred `createSpiralCarryoverIssue` and `markOpenQuestionAnsweredOnDisk`, Step 3b `[Backlog-Sammel]` overflow, discovery-issue creation |
|
|
@@ -36,6 +36,7 @@ process.stdout.write(JSON.stringify(tasks));
|
|
|
36
36
|
Run the accessor in `plan-verification.md § SESSION_START_REF accessor`; it exports `$SESSION_RANGE`, one complete diff range. Summary:
|
|
37
37
|
- Primary: the pinned `session-start-ref` from STATE.md frontmatter → `<sha>..HEAD`.
|
|
38
38
|
- Field absent: the baseline `resolveBaselineRange()` resolves → `<base>...HEAD` (never a hard-coded `origin/main`).
|
|
39
|
+
- Multi-tree (#1659): also run `node scripts/session-commit-files.mjs` and treat its union as additional changed files (commits in the session's other own worktrees). The own-tree range above stays authoritative; no diff across trees is computed; skipped trees (stderr) are unmeasured, not unchanged.
|
|
39
40
|
- Nothing resolves: `$SESSION_RANGE` is empty and the accessor names `no-baseline-ref` or `unresolvable-ref` — log `⚠ Phase 3.2: no session range (<reason>). Docs verification skipped.` and skip Phase 3.2. Never treat the empty range as "no docs changed".
|
|
40
41
|
|
|
41
42
|
If `git diff` itself fails (network issue, corrupt repo), log:
|
|
@@ -147,12 +147,12 @@ import { runTailPhases } from '${PLUGIN_ROOT}/scripts/lib/session-end/tail-runne
|
|
|
147
147
|
|
|
148
148
|
const tail = await runTailPhases({ repoRoot: process.cwd(), plan });
|
|
149
149
|
const sweep = tail['3.6.4'];
|
|
150
|
-
// { ran: true, scanned, archived, archivePath } | { ran: false, reason: 'plan-skip' | 'no-plan' | 'error', error? }
|
|
150
|
+
// { ran: true, scanned, archived, archivePath } | { ran: false, reason: 'plan-skip' | 'no-plan' | 'store-role-inbox' | 'error', error? }
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
- `runTailPhases` delegates to `runExpiredSweep({ repoRoot, plan, now })` — the same module's single-phase entry point — and returns a KEYED shape so a caller keeps working when a second phase becomes mechanical. Today exactly one phase is: 3.6.3, 3.6.5–3.6.8 stay coordinator-executed because they are AUQ-gated or need a subagent dispatch a library function cannot make.
|
|
154
154
|
- **Never throws, fails CLOSED.** Any error yields `{ ran: false, reason: 'error' }` and the close proceeds. Stale-past-grace entries move into `.orchestrator/metrics/learnings-archive.jsonl` (append-only, never deleted).
|
|
155
|
-
- **Report** `sweep.ran`, `sweep.scanned` and `sweep.archived` in the Phase 6 Final Report, e.g. `expired-sweep: 12 archived of 640 scanned`. When `ran: false`, report the `reason` instead — a skipped sweep is a stated outcome, never silence.
|
|
155
|
+
- **Report** `sweep.ran`, `sweep.scanned` and `sweep.archived` in the Phase 6 Final Report, e.g. `expired-sweep: 12 archived of 640 scanned`. When `ran: false`, report the `reason` instead — a skipped sweep is a stated outcome, never silence. `store-role-inbox` means this host only appends to `learnings-inbox.jsonl` (owner.yaml `learnings.store-role: inbox`, #1516); the writer host sweeps.
|
|
156
156
|
- **The proof it ran is the event `orchestrator.learnings.sweep_applied`** in `.orchestrator/metrics/events.jsonl` (payload source `session-end-3.6.4`, which separates it from the standalone CLI). A close claiming a sweep with no such event did not sweep.
|
|
157
157
|
|
|
158
158
|
The standalone `node scripts/sweep-expired-learnings.mjs --apply --json` CLI remains available for manual/out-of-session use; it is no longer the session-end path.
|
|
@@ -180,17 +180,19 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
|
|
|
180
180
|
|
|
181
181
|
2. Determine the **judged set** — only THIS session's selected skills.
|
|
182
182
|
|
|
183
|
-
> **Join on the RAW UUID, never on the semantic session id.** The writer is the `PreToolUse` hook `hooks/skill-invocation-telemetry.mjs`, which stamps `session_id` straight from the hook payload (`:190`) — that is the harness UUID. Measured 2026-09-19 over `.orchestrator/metrics/skill-invocations.jsonl` (841 lines): **767 raw UUIDs, 55 semantic ids, 17 null**. Joining on `main-<date>-session-N` therefore matches ~0 rows for a current session, the judged set comes back empty, and the phase reports `empty-input` — a silent no-op indistinguishable from "this session used no skills". The raw id is `process.env.CLAUDE_CODE_SESSION_ID
|
|
183
|
+
> **Join on the RAW UUID, never on the semantic session id.** The writer is the `PreToolUse` hook `hooks/skill-invocation-telemetry.mjs`, which stamps `session_id` straight from the hook payload (`:190`) — that is the harness UUID. Measured 2026-09-19 over `.orchestrator/metrics/skill-invocations.jsonl` (841 lines): **767 raw UUIDs, 55 semantic ids, 17 null**. Joining on `main-<date>-session-N` therefore matches ~0 rows for a current session, the judged set comes back empty, and the phase reports `empty-input` — a silent no-op indistinguishable from "this session used no skills". The raw id is `process.env.CLAUDE_CODE_SESSION_ID`; it is also the id that names the transcript file in step 3. The repo-global `.orchestrator/session.lock` may name a PEER session (#1664, same class as #1663), so its `session_id` counts only via `resolveSessionIds()`, which returns it only when it is among this process's own ids (`readProcessLocalSessionIds()`) and `null` otherwise. No provable id → empty judged set → `empty-input`, never a judgment of the peer's transcript.
|
|
184
184
|
|
|
185
185
|
```javascript
|
|
186
186
|
import { readFileSync } from 'node:fs';
|
|
187
|
-
import {
|
|
187
|
+
import { resolveSessionIds } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
|
|
188
188
|
|
|
189
|
+
// Lock id only when provably OWN — never a peer's (#1664).
|
|
189
190
|
const rawSessionId =
|
|
190
191
|
(process.env.CLAUDE_CODE_SESSION_ID || '').trim() ||
|
|
191
|
-
(
|
|
192
|
+
resolveSessionIds({ repoRoot: process.cwd() }).session_id;
|
|
192
193
|
|
|
193
|
-
|
|
194
|
+
// No id → no join: `null` would otherwise match the null-session rows.
|
|
195
|
+
const selectedSkills = !rawSessionId ? [] : [...new Set(
|
|
194
196
|
readFileSync('.orchestrator/metrics/skill-invocations.jsonl', 'utf8')
|
|
195
197
|
.split('\n').filter(Boolean)
|
|
196
198
|
.flatMap((l) => { try { return [JSON.parse(l)]; } catch { return []; } })
|
|
@@ -207,6 +209,7 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
|
|
|
207
209
|
import { buildSkillEvidence } from '${PLUGIN_ROOT}/scripts/lib/skill-evidence-window.mjs';
|
|
208
210
|
import { runSkillJudge, evidenceBudgetChars } from '${PLUGIN_ROOT}/scripts/lib/skill-judge.mjs';
|
|
209
211
|
import { appendSkillJudgment } from '${PLUGIN_ROOT}/scripts/lib/skill-judgments-schema.mjs';
|
|
212
|
+
import { resolveLedgerPath } from '${PLUGIN_ROOT}/scripts/lib/ledger-path.mjs';
|
|
210
213
|
import path from 'node:path';
|
|
211
214
|
|
|
212
215
|
const budgetTokens = config['skill-evolution']['judge-budget-tokens'] ?? 8000;
|
|
@@ -253,7 +256,8 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
|
|
|
253
256
|
4. On `result.status === 'ok'`: the **COORDINATOR** writes each returned judgment to the sidecar. Stamp the per-record metadata (`timestamp`, `event: 'judged'`, `session_id`, `advisory: true`, `model`, `schema_version: 1`) and append:
|
|
254
257
|
|
|
255
258
|
```javascript
|
|
256
|
-
|
|
259
|
+
// In a linked worktree the main checkout's ledger when it is gitignored there (#1722).
|
|
260
|
+
const judgmentsPath = resolveLedgerPath('.orchestrator/metrics/skill-judgments.jsonl', process.cwd());
|
|
257
261
|
const nowIso = new Date().toISOString();
|
|
258
262
|
for (const j of result.judgments) {
|
|
259
263
|
await appendSkillJudgment(
|
|
@@ -24,6 +24,10 @@ try {
|
|
|
24
24
|
// parallel-session CWD drift). Passing an undefined repoRoot here sends every close down
|
|
25
25
|
// the fail-open catch below and silently omits all 5 fields (GitLab #1036).
|
|
26
26
|
const repoRoot = execSync('git rev-parse --show-toplevel', {encoding: 'utf8'}).trim();
|
|
27
|
+
// SAME path Phase 3.4 writes `status: completed` into (#1640): the STATE.md
|
|
28
|
+
// this session's state-init wrote. Pass it explicitly — never let the
|
|
29
|
+
// resolver re-guess, or a foreign .claude/STATE.md can take the fields.
|
|
30
|
+
const statePath = '<state-dir>/STATE.md';
|
|
27
31
|
|
|
28
32
|
// In-memory session metrics — pulled from the session's running state,
|
|
29
33
|
// NOT re-read from sessions.jsonl (which was just-written in Phase 3.7).
|
|
@@ -41,8 +45,9 @@ try {
|
|
|
41
45
|
'rationale': rec.rationale,
|
|
42
46
|
};
|
|
43
47
|
|
|
44
|
-
await updateFrontmatterFieldsOnDisk(repoRoot, fields);
|
|
45
|
-
|
|
48
|
+
const res = await updateFrontmatterFieldsOnDisk(repoRoot, fields, {statePath});
|
|
49
|
+
if (res.reason) throw new Error('STATE.md not written (' + res.reason + '): ' + res.path);
|
|
50
|
+
console.log('Recommendations written to ' + res.path + ': ' + rec.mode + ' (' + rec.rationale + ')');
|
|
46
51
|
} catch (err) {
|
|
47
52
|
// AC3: defensive — exception must NOT block Phase 3.4 status: completed.
|
|
48
53
|
mkdirSync('.orchestrator/metrics', {recursive: true});
|
|
@@ -63,6 +68,8 @@ try {
|
|
|
63
68
|
|
|
64
69
|
**Repo-root contract (#1036 / Kanevry#65):** `updateFrontmatterFieldsOnDisk(repoRoot, fields)` takes the repo root as its FIRST argument and rejects a missing one via `requireRepoRoot()` — there is no implicit `process.cwd()` fallback, deliberately (parallel-session CWD drift, PSA rules). Run the snippet verbatim, including the `repoRoot` binding: an `undefined` first argument throws *before* STATE.md is touched, and the AC3 catch in the snippet turns that throw into a green close with all 5 fields missing. That failure is invisible in STATE.md — it looks exactly like "this session produced no recommendation".
|
|
65
70
|
|
|
71
|
+
**Shared path source (#1640):** `statePath` is the one `<state-dir>/STATE.md` value this close uses for BOTH Phase 3.7a and Phase 3.4 — substitute the identical path in both snippets. The writer still refuses a target that is another session's COMPLETED record (`reason: 'foreign-state-md'`, stderr names the file); it never overwrites such a record and never redirects the write. Phase 3.4 re-stamps `session-id` to the closing process's own id, so a re-run of this phase after 3.4 still finds its own file.
|
|
72
|
+
|
|
66
73
|
**Data source guarantee:** The three inputs (`completionRate`, `carryoverRatio`, `carryoverIssues`) MUST come from the in-memory session metrics object built in Phase 1.7, NOT from a re-read of `.orchestrator/metrics/sessions.jsonl`. Reading the just-written JSONL would introduce a circular dependency and risk reading a truncated line if Phase 3.7's `appendJsonl` was mid-flush.
|
|
67
74
|
|
|
68
75
|
**Field precision:**
|
|
@@ -43,6 +43,7 @@ Read back the session plan that was agreed at the start. For EACH planned item:
|
|
|
43
43
|
- **Verify with evidence**: read the changed files, check git diff, run relevant test
|
|
44
44
|
- Confirm acceptance criteria are met
|
|
45
45
|
- Mark as completed
|
|
46
|
+
- An `[Abnahme]` issue is never a Done item, even when its implementation landed this session — it goes to 1.1b (#1678)
|
|
46
47
|
|
|
47
48
|
### 1.1a File-Level Grounding
|
|
48
49
|
|
|
@@ -51,7 +52,7 @@ Read back the session plan that was agreed at the start. For EACH planned item:
|
|
|
51
52
|
Compare the files the plan said would be touched against the files actually changed in the session. Catches both **scope creep** (files changed that were not in any agent's prompt scope) and **incomplete coverage** (files in the plan that were never edited).
|
|
52
53
|
|
|
53
54
|
1. **Planned files** = union of all file paths from agent prompt scopes across all waves. Source: STATE.md Wave History, falling back to the original session plan's per-agent "Files:" specs. Glob patterns are expanded against the working tree at session-start time.
|
|
54
|
-
2. **Actual files** = `git diff --name-only "$SESSION_RANGE"` (accessor above: the pinned `session-start-ref`, else the resolved baseline range). Empty `$SESSION_RANGE` → skip 1.1a with the accessor's reason.
|
|
55
|
+
2. **Actual files** = `git diff --name-only "$SESSION_RANGE"` (accessor above: the pinned `session-start-ref`, else the resolved baseline range). Empty `$SESSION_RANGE` → skip 1.1a with the accessor's reason. **Multi-tree (#1659):** also run `node scripts/session-commit-files.mjs` (session id defaults to `$CLAUDE_CODE_SESSION_ID`; `--json` prints `{ trees, perTree, skipped, union, malformed }`) and add its union — files committed in the session's OTHER worktrees, recorded by `hooks/pre-bash-staging-fence.mjs` as `orchestrator.session.work_tree` events — to Actual. The own-tree `session-start-ref` range stays authoritative for the own tree; the CLI diffs each recorded tree only inside itself, never across trees. Trees it lists as skipped (stderr) were not measured: report them, do not read them as "no changes".
|
|
55
56
|
3. **Compute discrepancies:**
|
|
56
57
|
- **Touched** = files in both Planned and Actual
|
|
57
58
|
- **Unplanned (scope creep)** = files in Actual but not in Planned
|
|
@@ -83,6 +84,20 @@ Compare the files the plan said would be touched against the files actually chan
|
|
|
83
84
|
```
|
|
84
85
|
The metrics field is conditional on `grounding-check: true` — when the gate is off, omit the field entirely (do not write `null`).
|
|
85
86
|
|
|
87
|
+
### 1.1b External Acceptance (`[Abnahme]` issues, #1678)
|
|
88
|
+
|
|
89
|
+
`/plan` files every PRD § External Acceptance row as its own issue `[Abnahme] <Kriterium>` — real-world acceptance by the owner or an external oracle, which the implementation issue does not close. List the OPEN ones, separate from 1.1:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
glab issue list --search "[Abnahme]" --in title --per-page 100 # GitLab (open is the default)
|
|
93
|
+
gh issue list --search "[Abnahme] in:title" --state open --limit 100 # GitHub
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **Title filter:** keep only titles that START with `[Abnahme]` — GitHub search ignores punctuation and also matches e.g. "Abnahme-Checkliste".
|
|
97
|
+
- **Scope:** keep only issues linked to this session's planned issues or their epic (issue links, `Epic`/`Parent` reference in the body, shared epic label). If the link is not determinable, list all open `[Abnahme]` issues and say so in the report (`scope: all open — link not determinable`).
|
|
98
|
+
- **Per issue:** `#<IID> <Kriterium> — owner: <from the body, else assignee, else "Owner"> — not-before: <date from the body or a label, else —>`. `/plan` writes owner, not-before, pass condition, fallback and oracle command into the body (`skills/plan/SKILL.md` § 6.1), so read the body first.
|
|
99
|
+
- **Closing rule:** an `[Abnahme]` issue is closed ONLY with oracle evidence in the closing comment — the command plus its result, or the owner's observation, quoted and dated. A green test alone is not enough. session-end never closes one on its own because the implementation landed, and files no carryover candidate for it (it stays open by design); it reports it in Phase 6 under **Abnahme offen**.
|
|
100
|
+
|
|
86
101
|
### 1.2 Partially Done Items
|
|
87
102
|
- Document what was completed and what remains
|
|
88
103
|
- **Do NOT file the carryover issue here (#769).** Collect a carryover **candidate** instead — append it to the in-memory candidate list that the Phase 1.65 Handover Alignment Gate consumes. The issue is filed (only if the gate confirms it) in Phase 5 Step 3. Candidate record (JS keys as `routeCandidates` / `normalizeCandidate` read them — `source-phase`→`sourcePhase`, `origin-issue`→`originIssue`; see `plan-verification.md § Candidate Record Format`):
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
> // so a long-idle deep session's 4h-TTL lock does not lapse mid-close.
|
|
12
12
|
> // BEFORE Phase 3.8 lock-release (which deletes the lock).
|
|
13
13
|
> import { updateHeartbeat } from 'scripts/lib/session-lock.mjs';
|
|
14
|
-
>
|
|
14
|
+
> import { resolveOwnLockRootFor } from 'scripts/lib/own-lock-root.mjs';
|
|
15
|
+
> // The checkout holding this session's lock, not the cwd's (#1730) — at the wrong root nothing is written.
|
|
16
|
+
> updateHeartbeat({ sessionId, repoRoot: resolveOwnLockRootFor({ sessionId }).root });
|
|
15
17
|
> ```
|
|
16
18
|
>
|
|
17
19
|
> Skip silently if `persistence: false` in Session Config (no session.lock exists in that mode).
|
|
@@ -63,24 +65,24 @@ Review `<state-dir>/rules/` files that are relevant to this session's work:
|
|
|
63
65
|
|
|
64
66
|
### 3.4 Update STATE.md
|
|
65
67
|
|
|
66
|
-
> **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
|
|
68
|
+
> **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), the final `current-wave`, the re-stamped `session-id` (#1640, see step 1), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
|
|
67
69
|
|
|
68
70
|
> **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.6 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.7e → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md` — leaving four live tail phases, not six; the Tail-Diät skip-plan dispatcher dispatches exactly those four mechanically). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
|
|
69
71
|
|
|
70
72
|
> Gate: Only run if `persistence` is enabled in Session Config and `<state-dir>/STATE.md` exists.
|
|
71
|
-
1.
|
|
72
|
-
2. Record final wave count and completion time in the frontmatter
|
|
73
|
-
3. Touch `updated: <ISO 8601 UTC>` in the frontmatter (issue #184). Use `scripts/lib/state-md.mjs` → `touchUpdatedField` for safety:
|
|
73
|
+
1. Finalize the frontmatter in ONE lock-guarded write, into the SAME file Phase 3.7a wrote (#1640 — identical `statePath`, passed explicitly): `status: completed`, `updated: <ISO 8601 UTC>` (#184), the final wave count as `current-wave`, and `session-id` re-stamped to this process's own raw id when one is measurable (after a mid-session `/clear` the record still names the pre-`/clear` id; the re-stamp keeps it this session's). Do not hand-edit any of these fields:
|
|
74
74
|
```bash
|
|
75
75
|
node --input-type=module -e "
|
|
76
|
-
import {
|
|
77
|
-
import {
|
|
78
|
-
const
|
|
79
|
-
|
|
76
|
+
import {execSync} from 'node:child_process';
|
|
77
|
+
import {markStateMdCompletedOnDisk} from '${PLUGIN_ROOT}/scripts/lib/state-md/frontmatter-mutators.mjs';
|
|
78
|
+
const repoRoot = execSync('git rev-parse --show-toplevel', {encoding: 'utf8'}).trim();
|
|
79
|
+
const statePath = '<state-dir>/STATE.md'; // same value as Phase 3.7a
|
|
80
|
+
const res = await markStateMdCompletedOnDisk(repoRoot, {statePath, currentWave: <final wave number>});
|
|
81
|
+
console.log(res.written ? 'STATE.md completed: ' + res.path : 'STATE.md NOT written (' + (res.reason ?? 'no change') + '): ' + res.path);
|
|
80
82
|
"
|
|
81
83
|
```
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
No-op if the file has no frontmatter. `reason: 'foreign-state-md'` means the path holds another session's completed record — report it as a Deviation, never hand-edit it.
|
|
85
|
+
2. Keep the file as a record — do NOT delete it (next session-start reads it)
|
|
84
86
|
|
|
85
87
|
If STATE.md doesn't exist, skip this subsection.
|
|
86
88
|
|
|
@@ -233,7 +235,7 @@ const boardResult = await mirrorBoard({
|
|
|
233
235
|
|
|
234
236
|
Evaluate the just-closed session deterministically and, when configured, with an advisory LLM judge. This phase is a thin hook — the full evaluation flow lives in `skills/eval/SKILL.md`; only the close-out integration is described here.
|
|
235
237
|
|
|
236
|
-
1. **Deterministic run.** Invoke `node scripts/eval-session.mjs --json` — with no `--session`, the cascade (`resolveSession`, revised #822) walks records newest-to-oldest (source order) and evaluates the first one that is either `status:'completed'` or non-abandoned with evidence of completed work (typically the record Phase 3.7 just appended). Model capture: the coordinator passes `--model-id <id> --model-source self-report`; the `$ANTHROPIC_MODEL` env var wins automatically when set. Pass the configured pseudonym through with `--handle <eval.handle>` (omit when `null`). The CLI appends the eval record to `.orchestrator/metrics/eval.jsonl` (`appendEvalRecord` is never-throw).
|
|
238
|
+
1. **Deterministic run.** Invoke `node scripts/eval-session.mjs --json` — with no `--session`, the cascade (`resolveSession`, revised #822) walks records newest-to-oldest (source order) and evaluates the first one that is either `status:'completed'` or non-abandoned with evidence of completed work (typically the record Phase 3.7 just appended). Model capture: the coordinator passes `--model-id <id> --model-source self-report`; the `$ANTHROPIC_MODEL` env var wins automatically when set. Pass the configured pseudonym through with `--handle <eval.handle>` (omit when `null`). The CLI appends the eval record to `.orchestrator/metrics/eval.jsonl` (`appendEvalRecord` is never-throw); run from a linked worktree, that is the main checkout's journal when the worktree ignores it (`resolveLedgerPath`, #1722), so the record survives the worktree.
|
|
237
239
|
2. **Advisory judge (opt-in).** When `eval.judge` is not `off`, run the judge flow per `skills/eval/SKILL.md` § Phase 3: the coordinator dispatches the read-only `eval-judge` agent (DI'd dispatch, untrusted-data nonce fence), merges the advisory judge dimensions (`method: "judge"`, `advisory: true`, `calibration_status: "uncalibrated"`) into the record, and appends the merged record. When `eval.judge: off`, no agent is dispatched and no judge dimensions are produced.
|
|
238
240
|
3. **HTML report (opt-in).** When `eval.report: html` (the default), call `writeEvalReport(record, …)` from `scripts/lib/eval/report.mjs` to emit the self-contained run report under `.orchestrator/eval/reports/<run-id>.html` (gitignored, regenerable from the record). When `eval.report: none`, skip the report.
|
|
239
241
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
> **VCS Reference:** Use CLI commands per the "Common CLI Commands" section of the gitlab-ops skill.
|
|
8
8
|
|
|
9
|
-
1. **Close resolved issues — one command strips, closes and verifies (#308):** run the CLI ONCE with every resolved issue ID, `--vcs` taken from Session Config:
|
|
9
|
+
1. **Close resolved issues — one command strips, closes and verifies (#308):** run the CLI ONCE with every resolved issue ID, `--vcs` taken from Session Config. An `[Abnahme]` issue is never in that list: it closes only on oracle evidence (`plan-verification.md` § 1.1b, #1678).
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
node "$PLUGIN_ROOT/scripts/lib/issue-close-strip-labels.mjs" --close --vcs <gitlab|github> <iid> [<iid> ...]
|
|
@@ -11,6 +11,10 @@ Present to the user:
|
|
|
11
11
|
- [x] Issue #N: [description] — [evidence: tests passing, files changed]
|
|
12
12
|
- [x] Issue #M: [description]
|
|
13
13
|
|
|
14
|
+
### Abnahme offen (implemented ≠ accepted — never counted as Completed) [#1678]
|
|
15
|
+
- [ ] #<IID> [Abnahme] <Kriterium> — owner: <body | assignee | Owner> — not-before: <date | —>
|
|
16
|
+
- scope: [linked to this session's plan/epic | all open — link not determinable]
|
|
17
|
+
|
|
14
18
|
### Carried Over
|
|
15
19
|
- [ ] Issue #P: [what's left] — new issue #Q created
|
|
16
20
|
- [ ] [description] — blocked by [reason]
|
|
@@ -13,9 +13,10 @@
|
|
|
13
13
|
>
|
|
14
14
|
> Minimal invocation:
|
|
15
15
|
> ```bash
|
|
16
|
-
> printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs"
|
|
16
|
+
> printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs"
|
|
17
17
|
> ```
|
|
18
18
|
> See step 2 below for the full invocation including exit-code handling.
|
|
19
|
+
> Pass no `--file`: the default is the main checkout's `sessions.jsonl` (#1584). A relative `--file` is taken verbatim and, run from a linked worktree, writes the worktree's own copy, which is gitignored here and lost with the worktree (#1722).
|
|
19
20
|
|
|
20
21
|
1. Ensure `.orchestrator/metrics/` directory exists: `mkdir -p .orchestrator/metrics`
|
|
21
22
|
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
|
|
30
31
|
> **#701.2 DOC NOTE — `completed_at >= started_at` guard:** This invariant is enforced mechanically by `scripts/emit-session.mjs`. The writer applies `clampTimestampsMonotonic()` (from `scripts/lib/session-schema/timestamps.mjs`) before `validateSession()`, clamping any inversion of `completed_at < started_at` to `started_at` and recording forensics in `_clamped: true` / `_original_completed_at`. Previously-inverted entries (e.g. `main-2026-06-21-session-4`) are already corrected. **No per-session coordinator action is needed** — the writer enforces the invariant at write time. Do not add defensive clamping logic here; the canonical guard lives in `emit-session.mjs`.
|
|
31
32
|
|
|
32
|
-
1a. **Token Rollup (#644, #1244 — mechanical since #1436)** — no coordinator action. `scripts/emit-session.mjs` (step 2) runs `rollupSessionTokens()` itself whenever the incoming record carries no `total_tokens`: it resolves the session's raw UUID from `.orchestrator/current-session.json` (adopted only when its `semantic_session_id` equals the record's `session_id` and no process-local session id contradicts it), joins the sibling `subagents.jsonl` on `parent_session_id`, and fills `total_tokens`, `total_token_input`, `total_token_output`, the three cache buckets, `total_cost_usd`, `subagents_with_tokens`, `matched_records`, `cost_records_priced`, `cost_records_total` and `_token_schema` for every key the record does not already carry — plus `raw_session_id`. No matching records, an unreadable ledger or a foreign marker omits the fields with a WARN on stderr, never a fabricated `0`. An explicit key on `$METRICS_ENTRY` still wins. The same UUID also backs `session_start_ref` when STATE.md has none (the `head_sha` of the session's own `orchestrator.session.started` event).
|
|
33
|
+
1a. **Token Rollup (#644, #1244 — mechanical since #1436)** — no coordinator action. `scripts/emit-session.mjs` (step 2) runs `rollupSessionTokens()` itself whenever the incoming record carries no `total_tokens`: it resolves the session's raw UUID from `.orchestrator/current-session.json` (adopted only when its `semantic_session_id` equals the record's `session_id` and no process-local session id contradicts it), joins the sibling `subagents.jsonl` on `parent_session_id`, and fills `total_tokens`, `total_token_input`, `total_token_output`, the three cache buckets, `total_cost_usd`, `subagents_with_tokens`, `matched_records`, `cost_records_priced`, `cost_records_total` and `_token_schema` for every key the record does not already carry — plus `raw_session_id`. No matching records, an unreadable ledger or a foreign marker omits the fields with a WARN on stderr, never a fabricated `0`. An explicit key on `$METRICS_ENTRY` still wins. The same UUID also backs `session_start_ref` when STATE.md has none (the `head_sha` of the session's own `orchestrator.session.started` event) — except when the session's own registry entry shows `work_root` ≠ `session_root` (#1652): that sha was measured in the SessionStart root, not the tree the session works in, so the field is omitted with a WARN.
|
|
33
34
|
|
|
34
35
|
**Semantics:** `null` totals mean "no token data was captured for this session" — this is NOT the same as zero cost. Do NOT coerce null to 0 when displaying or summing across sessions. The same holds for `total_cost_usd`: it is omitted whenever `cost_records_priced < cost_records_total` (#1475) — at least one cost-relevant subagent record could not be priced (a model the price table does not know, a token-bearing turn naming no model, a transcript that yielded no tokens, e.g. one over the hook's size cap, or an agent with a start record whose stop found no transcript of its own, mostly `workflow-subagent`, or an agent known only from `schema_version` 1 records — #1487 Pkt 1) — and also when `cost_records_total` is 0, because there was nothing to price. It is never "$0" for an unknown cost and never a partial sum over the v2 agents the ledger can see. Phantom stops (#939, no start record) are no agents and never count. The two counters are persisted beside it, so a missing cost says whether part of the session was unpriceable or nothing was priceable. Blind spots remain: an agent whose start record fell outside the hook's tail window looks like a phantom, an agent with neither start nor stop record is invisible. A session spanning the 2026-09-09 schema boundary therefore reports no cost (its v1 agents are unpriced candidates since 2026-10-02; before, it was priced over its v2 records only — 4 distinct fleet sessions carried such a partial sum). A subagent whose turns span several models carries a per-model `models_usage` breakdown in `subagents.jsonl` (#1470) and is priced part by part. A written `total_cost_usd` is the **API list-price equivalent** of the metered tokens, never a payment: subscription quota, purchased credits and the amount actually billed are invisible to the rollup, and no record states any of them. Since #1470 emit-session marks this with `cost_basis: "api-list-equivalent"` beside every `total_cost_usd` it writes, and writes `pricing_table_date` (the `PRICING_TABLE_DATE` of `scripts/lib/telemetry/pricing.mjs` that priced the session) beside the cost counters. A record without these fields predates #1470; its cost is a list-price estimate too, priced by an older table, not a verified bill. The emit-session warning names the reason via `match_status` (ledger absent, ledger with no readable records, or records present but none for the own raw UUID); none of these means zero cost.
|
|
35
36
|
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
|
|
40
41
|
2. Append the prepared JSONL entry (from Phase 1.7; the writer itself adds the token fields — step 1a) via the validating writer `scripts/emit-session.mjs` (issue #249):
|
|
41
42
|
```bash
|
|
42
|
-
printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs"
|
|
43
|
+
printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs"
|
|
43
44
|
EMIT_EXIT=$?
|
|
44
45
|
if [[ $EMIT_EXIT -eq 1 ]]; then
|
|
45
46
|
echo "ERROR: session-end validation failed — entry rejected by scripts/emit-session.mjs. See stderr above. Session metrics NOT written." >&2
|
|
@@ -199,7 +200,9 @@
|
|
|
199
200
|
# gate. Non-blocking: a failure surfaces a warning and never blocks close,
|
|
200
201
|
# using the same strict/warn degradation idiom as the vault-mirror step above.
|
|
201
202
|
NM_OUTPUT=$(node -e "
|
|
202
|
-
import('
|
|
203
|
+
import('node:url').then(({ pathToFileURL }) =>
|
|
204
|
+
import(pathToFileURL('$PLUGIN_ROOT/scripts/lib/vault-status/narrative-mirror.mjs').href)
|
|
205
|
+
).then(async (m) => {
|
|
203
206
|
const r = await m.mirrorNarrative({ repoRoot: process.cwd() });
|
|
204
207
|
process.stdout.write(JSON.stringify(r));
|
|
205
208
|
}).catch((e) => { process.stderr.write(String(e && e.message || e)); process.exit(3); });
|
|
@@ -231,7 +231,7 @@ It prints one JSON line carrying:
|
|
|
231
231
|
- `wavesConfigHonored` — whether the Session Config `waves` value was used
|
|
232
232
|
- `notes` — human-readable reasons for any of the above
|
|
233
233
|
|
|
234
|
-
**By default, the plan's wave list IS that output.** The coordinator fills tasks into the returned waves without adding, removing, or renumbering them. Exceptions are the empty-role rule below (and its coordinator-direct carve-out) and the user-authorized housekeeping execution deviation above. `--known-scope true` is what drops the Discovery wave on a deep session; `--profile ultradeep` is what selects the ultradeep shape, and it applies ONLY when STATE.md frontmatter carries `session-profile: ultradeep` (written by the `/session ultradeep` argument alias — see `commands/session.md`). `session-type` stays `deep`; the profile changes the wave SHAPE, nothing else, and it ignores the Session Config `waves` value (the shape says so in `wavesConfigHonored` / `notes`). Spec:
|
|
234
|
+
**By default, the plan's wave list IS that output.** The coordinator fills tasks into the returned waves without adding, removing, or renumbering them. Exceptions are the empty-role rule below (and its coordinator-direct carve-out) and the user-authorized housekeeping execution deviation above. `--known-scope true` is what drops the Discovery wave on a deep session; `--profile ultradeep` is what selects the ultradeep shape, and it applies ONLY when STATE.md frontmatter carries `session-profile: ultradeep` (written by the `/session ultradeep` argument alias — see `commands/session.md`). `session-type` stays `deep`; the profile changes the wave SHAPE, nothing else, and it ignores the Session Config `waves` value (the shape says so in `wavesConfigHonored` / `notes`). Spec: the internal ultradeep session-profile PRD (2026-09-06, not in the public mirror) § 5.
|
|
235
235
|
|
|
236
236
|
**Ultradeep agent counts per wave:** take each wave's cap from that wave's `agentCap` in the shape — there is no second table here to disagree with it. The caps are ceilings, not targets, and the Quality wave's cap is still EARNED per the Step 3 rule (the shape marks it `qualityEarned: true`); Research and Code-Discovery share wave 1's cap across their two separately-scoped groups; the Synthesis-Gate wave carries `agentCap: 0` with `coordinatorDirect: true` and writes only the coordinator's own artifacts (audit report, STATE.md, plan).
|
|
237
237
|
|
|
@@ -268,7 +268,7 @@ When `docs-orchestrator.enabled: true`, apply the following concrete dispatch ru
|
|
|
268
268
|
|
|
269
269
|
- `agents: 0` on such an item is a DECLARATION, never a defect — do not "fix" it upward, and do not let the Step 3.5 constraint check or the Step 3 tier table raise it.
|
|
270
270
|
- The wave still counts toward `total-waves` and still occupies its wave number; the renumbering above skips over it, it does not absorb it.
|
|
271
|
-
- The ultradeep Synthesis-Gate (wave 2) is the only such wave today. Without this exception the empty-role rule deletes it — and it is the one wave whose entire job is to stop and ask before any code is written (
|
|
271
|
+
- The ultradeep Synthesis-Gate (wave 2) is the only such wave today. Without this exception the empty-role rule deletes it — and it is the one wave whose entire job is to stop and ask before any code is written (the internal ultradeep session-profile PRD (2026-09-06, not in the public mirror) AC-4).
|
|
272
272
|
- The exception is scoped to the MARKER, not to the profile: any future coordinator-direct wave inherits it without another edit here.
|
|
273
273
|
|
|
274
274
|
### Role Details
|
|
@@ -446,7 +446,9 @@ The wait between presenting this plan and the operator's answer is the LONGEST u
|
|
|
446
446
|
// #1229 — the plan-approval wait is the longest unheartbeated stretch in the flow.
|
|
447
447
|
// Best-effort: a failure must NOT block the handoff to wave-executor.
|
|
448
448
|
import { updateHeartbeat } from 'scripts/lib/session-lock.mjs';
|
|
449
|
-
|
|
449
|
+
import { resolveOwnLockRootFor } from 'scripts/lib/own-lock-root.mjs';
|
|
450
|
+
// The checkout holding this session's lock, not the cwd's (#1730) — at the wrong root nothing is written.
|
|
451
|
+
updateHeartbeat({ sessionId, repoRoot: resolveOwnLockRootFor({ sessionId }).root });
|
|
450
452
|
```
|
|
451
453
|
|
|
452
454
|
- **Ownership-guarded — this cannot steal a peer's lock.** `updateHeartbeat` (`scripts/lib/session-lock.mjs`) reads the lock first and returns `false` WITHOUT writing when `session_id` differs from the one passed, when no lock exists, or when `sessionId` is empty. The write is reached only for a lock this session already owns.
|
|
@@ -77,10 +77,14 @@ Read and parse Session Config per `skills/_shared/config-reading.md`. Store resu
|
|
|
77
77
|
|
|
78
78
|
```javascript
|
|
79
79
|
try {
|
|
80
|
+
const { pathToFileURL } = await import('node:url');
|
|
80
81
|
const { appendSkillInvocation, DEFAULT_SKILL_INVOCATIONS_PATH } =
|
|
81
|
-
await import('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs');
|
|
82
|
-
const
|
|
83
|
-
|
|
82
|
+
await import(pathToFileURL('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs').href);
|
|
83
|
+
const { resolveLedgerPath } =
|
|
84
|
+
await import(pathToFileURL('${PLUGIN_ROOT}/scripts/lib/ledger-path.mjs').href);
|
|
85
|
+
// In a linked worktree the main checkout's ledger when it is gitignored there,
|
|
86
|
+
// so the record survives the worktree (#1722).
|
|
87
|
+
await appendSkillInvocation(resolveLedgerPath(DEFAULT_SKILL_INVOCATIONS_PATH, process.cwd()), {
|
|
84
88
|
timestamp: new Date().toISOString(),
|
|
85
89
|
event: 'selected',
|
|
86
90
|
skill: 'session-orchestrator:session-start',
|
|
@@ -350,6 +354,8 @@ Invokes `buildLiveSignals` (single SSOT for the signals shape) then `selectMode(
|
|
|
350
354
|
|
|
351
355
|
**See `phase-7-5-mode-selector.md` for full details.**
|
|
352
356
|
|
|
357
|
+
If session-start aborts without work scope (no candidates, nothing to do), send the one report required by `skills/_shared/fleet-protocol.md` § Rücklauf an den Navigator (Pflicht) in this turn (wait case `keine-arbeit`) before returning, instead of waiting silently, and end the answer with its wait sentence.
|
|
358
|
+
|
|
353
359
|
## Phase 7.6: Fleet Check-in (#1462)
|
|
354
360
|
|
|
355
361
|
> Skip silently when `persistence: false` (no ledger for the event). Runs BEFORE the first writing action; everything not stated here is in `skills/_shared/fleet-protocol.md`. Without fleet integration the CLI itself skips (stdout `skipped: "no-fleet"`, no file, no event, #1559): then this phase ends there — no hint, no `auflagen/` read, and no Standard-Auflagen, except for a headless run (`plattform: kopflos`), which applies them at once.
|
|
@@ -51,8 +51,8 @@ on it there fired the *critical* threshold on 84.0% of starts.
|
|
|
51
51
|
| Signal | Threshold | Class |
|
|
52
52
|
|---|---|---|
|
|
53
53
|
| CPU, judged on **min(1m, 5m)** (#943) | above `cpu-load-max-pct` (default 90) | soft |
|
|
54
|
-
| Live peer **sessions** from the registry | ≥ `concurrent-sessions-warn` (default 5) |
|
|
55
|
-
| Claude **processes** (fallback only, registry unreadable) | ≥ threshold × 6 |
|
|
54
|
+
| Live peer **sessions** from the registry | ≥ `concurrent-sessions-warn` (default 5) | **info** (reported, never counted — standing condition, #1654) |
|
|
55
|
+
| Claude **processes** (fallback only, registry unreadable) | ≥ threshold × 6 | **info** (same state, same rule — #1654) |
|
|
56
56
|
| Swap, **only while memory is unhealthy** | `> 3072 MB` hard / `> 1024 MB` soft | both |
|
|
57
57
|
| Zombie processes with a live peer/process context | ≥ 1 | **info** (reported, never counted — see HR-104) |
|
|
58
58
|
| SSH detected AND `ssh-no-docker: true` | — | info note |
|
|
@@ -70,10 +70,16 @@ caller can log which axes fired rather than re-deriving them from prose.
|
|
|
70
70
|
**Unit note (#1089):** `concurrent-sessions-warn` is denominated in SESSIONS. It
|
|
71
71
|
was compared against `claude_processes_count` until this rebuild — a measured 6x
|
|
72
72
|
unit error (median processes:sessions = 6.0 over 1461 paired samples) that made
|
|
73
|
-
the threshold fire on 93.6% of starts instead of 4.2%.
|
|
73
|
+
the threshold fire on 93.6% of starts instead of 4.2%. The 4.2% did not hold
|
|
74
|
+
(re-measured 2026-10-17, #1650: 67.4% M4 / 25.7% M5 since 2026-08-24). `probe()` now supplies
|
|
74
75
|
`peer_sessions_count` from the session registry (`detectPeers()`, self excluded,
|
|
75
76
|
heartbeat-fresh); the rescaled process count is a fallback for hosts where the
|
|
76
|
-
registry is unreadable.
|
|
77
|
+
registry is unreadable. **Report-only since #1654:** a state present on two thirds
|
|
78
|
+
of M4 starts is a standing condition, and HR-104 forbids counting one as a second
|
|
79
|
+
opinion — so the peer count (and its process fallback) appears as an `info:`
|
|
80
|
+
reason but never enters `signals.soft`, neither here nor in the inter-wave gate
|
|
81
|
+
(`evaluateWaveResourceGate()` delegates to `evaluate()`). The rare concurrency
|
|
82
|
+
signal is the session-start banner's start wave (`recent_starts_count`, #1650).
|
|
77
83
|
|
|
78
84
|
**CPU methodology (#943):** the gate/probe runs right after the coordinator's own
|
|
79
85
|
CPU-saturating quality-gate run by construction, so the 1-minute load average
|
|
@@ -90,7 +96,7 @@ to `cpu_load_pct` alone.
|
|
|
90
96
|
Print a one-line Resource Health verdict immediately after Phase 4's output:
|
|
91
97
|
|
|
92
98
|
```
|
|
93
|
-
Resource Health: ⚠ warn — two signals agree (
|
|
99
|
+
Resource Health: ⚠ warn — two signals agree (memory + cpu); capping agents-per-wave at 2.
|
|
94
100
|
```
|
|
95
101
|
|
|
96
102
|
On `green` with a reported signal, print the line but say plainly that nothing
|
|
@@ -92,7 +92,7 @@ Hand off to Phase 9 as usual. The coordinator then executes the 1-wave plan sess
|
|
|
92
92
|
1. Proceed to Phase 9 (session-plan handoff) carrying the banner. session-plan short-circuits to the 1-wave `coordinator-direct` plan — the same shape `scripts/session-shape.mjs` resolves for `housekeeping`; `/go` detects it and does NOT invoke wave-executor.
|
|
93
93
|
2. **Run the maintenance loop FIRST** (`skills/session-start/SKILL.md` Phase 7, "For housekeeping sessions"): drift-check work-list, expired sweep, `/evolve analyze`, `/reconcile`, `/evolve dialectic` (dry-run then apply), `/memory-cleanup` — each AUQ-gated. `coordinator-direct` means no wave-executor, NOT zero subagents: the dialectic step dispatches the read-only `dialectic-deriver`. The operator-selected tasks run after the loop, because the loop's outputs (new learnings, new rules) are inputs the task work should already see.
|
|
94
94
|
3. For each agreed task (in dependency order): execute as a direct coordinator action — read files, make changes, run quality checks inline. No subagents, no inter-wave checkpoints.
|
|
95
|
-
4. Log the express-path activation in STATE.md `## Deviations` section: `Express path: N tasks executed coord-direct (express-path.enabled: true, session-type: housekeeping, scope: N issues)` — written BEFORE session-end is invoked. Then invoke `skills/session-end/SKILL.md` directly.
|
|
95
|
+
4. Log the express-path activation in STATE.md `## Deviations` section: `Express path: N tasks executed coord-direct (express-path.enabled: true, session-type: housekeeping, scope: N issues)` — written BEFORE session-end is invoked. Then invoke `skills/session-end/SKILL.md` directly — **unless the session is lead-guided** (fleet integration, `flotte: true` per `skills/_shared/fleet-protocol.md` § Rücklauf an den Navigator): then it ends with summary, report, hand-over and `Bereit zum Schließen.` as the last sentence instead, and the coordinator triggers `/close` (fleet-protocol § Übergabe Punkt 4, #1620). Step 5 does not run on this path: the regular session-end flow of that `/close` finalizes STATE.md.
|
|
96
96
|
5. After session-end completes successfully: verify STATE.md `status` is `completed` and `## Deviations` contains the express-path entry from step 4. If either is missing, warn the user with a one-line note and instructions to re-run `/close` manually. Then return the final session summary to the user.
|
|
97
97
|
|
|
98
98
|
**Persistence contract:**
|
|
@@ -101,6 +101,7 @@ Step 1 is the Phase 9 handoff and ends the session-start turn — the operator t
|
|
|
101
101
|
|
|
102
102
|
- Step 2 (maintenance loop) and step 3 (execute tasks) happen first in that turn's main flow, in that order.
|
|
103
103
|
- Step 4a (deviations log) is written BEFORE session-end is invoked. The coordinator calls `appendDeviation()` from `scripts/lib/state-md.mjs` to append the `Express path:` bullet to the `## Deviations` section while STATE.md is still `status: active`.
|
|
104
|
+
- Lead-guided exception (#1620): with fleet integration, steps 4b and 5 do not run — the turn ends with the hand-over after step 4a, and session-end runs when the coordinator triggers `/close`.
|
|
104
105
|
- Step 4b (invoke session-end) flips `status` to `completed`, writes the metrics record to `.orchestrator/metrics/sessions.jsonl`, and runs the standard close flow. Session-end has no Express Path-specific logic — it treats this run identically to any other completed session.
|
|
105
106
|
- Step 5 (verification) is the coordinator's final action before returning control. The verification check uses `parseStateMd()` from `scripts/lib/state-md.mjs` to read the file and check `frontmatter.status === 'completed'` and that the body contains the literal string `Express path:`.
|
|
106
107
|
|
|
@@ -125,5 +126,5 @@ Proceed normally to Phase 9 (session-plan handoff). The express-path evaluation
|
|
|
125
126
|
- `scripts/express-path.mjs` — the CLI this phase runs; `scripts/lib/express-path.mjs` holds the decision + its `orchestrator.express_path.evaluated` record
|
|
126
127
|
- `skills/session-plan/SKILL.md` § "Express Path Short-Circuit (#214)" — the 1-wave plan Phase 9 emits when the banner is present
|
|
127
128
|
- `skills/go/SKILL.md` — Express Path detection and auto-invocation of session-end after coord-direct tasks
|
|
128
|
-
- `skills/session-end/SKILL.md` —
|
|
129
|
-
- `skills/close/SKILL.md` —
|
|
129
|
+
- `skills/session-end/SKILL.md` — the session-end workflow express-path auto-invokes directly, bypassing the `/close` pre-check
|
|
130
|
+
- `skills/close/SKILL.md` — its pre-check (`scripts/close-precheck.mjs`) stops `/close` with verdict `already-finalized` when STATE.md is `completed` and recorded; that wording is what the user sees if express-path persistence breaks
|