session-orchestrator 5.1.0 → 5.3.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/.agents/skills/architecture/SKILL.md +3 -1
- package/.agents/skills/autopilot/SKILL.md +6 -1
- package/.agents/skills/autopilot/agents/openai.yaml +5 -0
- package/.agents/skills/bootstrap/SKILL.md +7 -1
- package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
- package/.agents/skills/brainstorm/SKILL.md +8 -1
- package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
- package/.agents/skills/close/SKILL.md +21 -0
- package/.agents/skills/close/agents/openai.yaml +5 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
- package/.agents/skills/debug/SKILL.md +7 -1
- package/.agents/skills/debug/agents/openai.yaml +5 -0
- package/.agents/skills/discovery/SKILL.md +7 -2
- package/.agents/skills/discovery/agents/openai.yaml +5 -0
- package/.agents/skills/dispatcher/SKILL.md +7 -1
- package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ecosystem-health/SKILL.md +3 -1
- package/.agents/skills/eli5/SKILL.md +7 -1
- package/.agents/skills/eli5/agents/openai.yaml +5 -0
- package/.agents/skills/eval/SKILL.md +7 -2
- package/.agents/skills/eval/agents/openai.yaml +5 -0
- package/.agents/skills/evolve/SKILL.md +8 -3
- package/.agents/skills/evolve/agents/openai.yaml +5 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
- package/.agents/skills/gitlab-ops/SKILL.md +3 -1
- package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
- package/.agents/skills/go/SKILL.md +22 -0
- package/.agents/skills/go/agents/openai.yaml +5 -0
- package/.agents/skills/grill/SKILL.md +7 -1
- package/.agents/skills/grill/agents/openai.yaml +5 -0
- package/.agents/skills/harness-audit/SKILL.md +20 -0
- package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
- package/.agents/skills/hook-development/SKILL.md +3 -1
- package/.agents/skills/mcp-builder/SKILL.md +3 -1
- package/.agents/skills/memory-cleanup/SKILL.md +6 -1
- package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
- package/.agents/skills/mode-selector/SKILL.md +3 -1
- package/.agents/skills/npm-publish/SKILL.md +4 -2
- package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
- package/.agents/skills/persona-panel/SKILL.md +6 -1
- package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
- package/.agents/skills/plan/SKILL.md +8 -2
- package/.agents/skills/plan/agents/openai.yaml +5 -0
- package/.agents/skills/playwright-driver/SKILL.md +3 -1
- package/.agents/skills/portfolio/SKILL.md +21 -0
- package/.agents/skills/portfolio/agents/openai.yaml +5 -0
- package/.agents/skills/quality-gates/SKILL.md +3 -1
- package/.agents/skills/reconcile/SKILL.md +6 -1
- package/.agents/skills/reconcile/agents/openai.yaml +5 -0
- package/.agents/skills/release/SKILL.md +22 -0
- package/.agents/skills/release/agents/openai.yaml +5 -0
- package/.agents/skills/remote-offload/SKILL.md +3 -1
- package/.agents/skills/repo-audit/SKILL.md +6 -1
- package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
- package/.agents/skills/session/SKILL.md +21 -0
- package/.agents/skills/session/agents/openai.yaml +5 -0
- package/.agents/skills/session-end/SKILL.md +3 -1
- package/.agents/skills/session-plan/SKILL.md +3 -1
- package/.agents/skills/session-start/SKILL.md +3 -1
- package/.agents/skills/spinout/SKILL.md +6 -1
- package/.agents/skills/spinout/agents/openai.yaml +5 -0
- package/.agents/skills/sunset-review/SKILL.md +7 -1
- package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
- package/.agents/skills/templates-ack/SKILL.md +21 -0
- package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
- package/.agents/skills/test/SKILL.md +21 -0
- package/.agents/skills/test/agents/openai.yaml +5 -0
- package/.agents/skills/test-runner/SKILL.md +3 -1
- package/.agents/skills/tmux-layout/SKILL.md +3 -1
- package/.agents/skills/using-orchestrator/SKILL.md +3 -1
- package/.agents/skills/ux-grill/SKILL.md +7 -1
- package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
- package/.agents/skills/vault-mirror/SKILL.md +3 -1
- package/.agents/skills/vault-sync/SKILL.md +3 -1
- package/.agents/skills/wave-executor/SKILL.md +3 -1
- package/.agents/skills/write-executable-plan/SKILL.md +3 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +4 -4
- package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
- package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
- package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
- package/.codex-plugin/skills/close/SKILL.md +3 -3
- package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
- package/.codex-plugin/skills/debug/SKILL.md +11 -4
- package/.codex-plugin/skills/discovery/SKILL.md +8 -4
- package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
- package/.codex-plugin/skills/eli5/SKILL.md +9 -4
- package/.codex-plugin/skills/eval/SKILL.md +9 -4
- package/.codex-plugin/skills/evolve/SKILL.md +9 -4
- package/.codex-plugin/skills/go/SKILL.md +3 -3
- package/.codex-plugin/skills/grill/SKILL.md +11 -4
- package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
- package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
- package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
- package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
- package/.codex-plugin/skills/plan/SKILL.md +8 -4
- package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
- package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
- package/.codex-plugin/skills/release/SKILL.md +3 -3
- package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
- package/.codex-plugin/skills/session/SKILL.md +1 -1
- package/.codex-plugin/skills/spinout/SKILL.md +4 -4
- package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
- package/.codex-plugin/skills/test/SKILL.md +3 -3
- package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
- package/.cursor/commands/autopilot.md +4 -4
- package/.cursor/commands/bootstrap.md +5 -4
- package/.cursor/commands/brainstorm.md +5 -4
- package/.cursor/commands/close.md +4 -3
- package/.cursor/commands/debug.md +4 -4
- package/.cursor/commands/discovery.md +4 -4
- package/.cursor/commands/dispatcher.md +4 -4
- package/.cursor/commands/eli5.md +4 -4
- package/.cursor/commands/eval.md +4 -4
- package/.cursor/commands/evolve.md +4 -4
- package/.cursor/commands/go.md +4 -3
- package/.cursor/commands/grill.md +4 -4
- package/.cursor/commands/harness-audit.md +3 -3
- package/.cursor/commands/memory-cleanup.md +4 -4
- package/.cursor/commands/persona-panel.md +4 -4
- package/.cursor/commands/plan.md +5 -4
- package/.cursor/commands/portfolio.md +3 -3
- package/.cursor/commands/reconcile.md +4 -4
- package/.cursor/commands/release.md +4 -3
- package/.cursor/commands/repo-audit.md +4 -4
- package/.cursor/commands/session.md +1 -1
- package/.cursor/commands/spinout.md +4 -4
- package/.cursor/commands/sunset-review.md +4 -4
- package/.cursor/commands/test.md +3 -3
- package/.cursor/commands/ux-grill.md +4 -4
- package/.cursor/rules/000-session-orchestrator.mdc +0 -2
- package/.cursor/rules/010-session-workflow.mdc +2 -2
- package/.cursor/rules/050-plan.mdc +1 -1
- package/.cursor/skills/bootstrap/SKILL.md +1 -0
- package/.cursor/skills/close/SKILL.md +13 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
- package/.cursor/skills/debug/SKILL.md +0 -1
- package/.cursor/skills/discovery/SKILL.md +0 -1
- package/.cursor/skills/dispatcher/SKILL.md +0 -1
- package/.cursor/skills/eli5/SKILL.md +0 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/evolve/SKILL.md +0 -1
- package/.cursor/skills/go/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +0 -1
- package/.cursor/skills/harness-audit/SKILL.md +12 -0
- package/.cursor/skills/npm-publish/SKILL.md +1 -0
- package/.cursor/skills/portfolio/SKILL.md +12 -0
- package/.cursor/skills/release/SKILL.md +13 -0
- package/.cursor/skills/repo-audit/SKILL.md +0 -1
- package/.cursor/skills/sunset-review/SKILL.md +0 -1
- package/.cursor/skills/test/SKILL.md +12 -0
- package/.cursor/skills/ux-grill/SKILL.md +0 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +13 -4
- package/AGENTS.md +3 -2
- package/CHANGELOG.md +197 -0
- package/README.md +11 -9
- package/SECURITY.md +12 -0
- package/agents/dialectic-deriver.md +13 -10
- package/agents/eval-judge.md +67 -45
- package/agents/skill-applied-judge.md +34 -19
- package/commands/session.md +17 -3
- package/docs/baseline.md +12 -6
- package/docs/ci-setup.md +53 -0
- package/docs/codex-setup.md +15 -3
- package/docs/components.md +13 -6
- package/docs/events-schema.md +59 -9
- package/docs/install.md +16 -0
- package/docs/persona-panel.md +1 -1
- package/docs/pi-setup.md +1 -1
- package/docs/rule-authoring.md +135 -14
- package/docs/scope-collision-guard.md +2 -0
- package/docs/session-config-reference.md +106 -11
- package/docs/session-config-template.md +31 -2
- package/docs/telemetry.md +2 -0
- package/hooks/_lib/hook-import-set.json +125 -8
- package/hooks/_lib/subagent-paths.mjs +15 -0
- package/hooks/_lib/subagent-transcript.mjs +582 -31
- package/hooks/_lib/vcs-create-matcher.mjs +217 -62
- package/hooks/config-protection.mjs +11 -3
- package/hooks/cwd-change-restore.mjs +11 -3
- package/hooks/enforce-commands.mjs +70 -23
- package/hooks/enforce-scope.mjs +143 -33
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +1 -1
- package/hooks/loop-guard.mjs +11 -3
- package/hooks/on-session-end.mjs +72 -25
- package/hooks/on-session-start.mjs +48 -11
- package/hooks/on-stop.mjs +211 -23
- package/hooks/operator-steer.mjs +11 -3
- package/hooks/post-bash-issue-budget-refund.mjs +18 -8
- package/hooks/post-bash-write-verify.mjs +6 -2
- package/hooks/post-edit-import-probe.mjs +17 -9
- package/hooks/post-edit-validate.mjs +13 -5
- package/hooks/post-subagent-discovery-validator.mjs +98 -13
- package/hooks/post-tool-batch-wave-signal.mjs +200 -38
- package/hooks/post-tool-failure-corrective-context.mjs +11 -5
- package/hooks/post-tooluse-frontend-slop.mjs +10 -4
- package/hooks/pre-auq-clarity.mjs +18 -2
- package/hooks/pre-bash-destructive-guard.mjs +80 -9
- package/hooks/pre-bash-issue-budget.mjs +119 -28
- package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
- package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
- package/hooks/pre-bash-staging-fence.mjs +335 -31
- package/hooks/pre-bash-templates-first.mjs +19 -14
- package/hooks/pre-task-scope-disjoint.mjs +385 -5
- package/hooks/skill-invocation-telemetry.mjs +2 -1
- package/hooks/subagent-telemetry.mjs +15 -19
- package/hooks/wave-scope-commit-guard.mjs +197 -100
- package/monitors/monitors.json +1 -1
- package/output-styles/wave-summary.md +1 -1
- package/package.json +2 -1
- package/pi/prompts/autopilot.md +3 -3
- package/pi/prompts/bootstrap.md +3 -3
- package/pi/prompts/brainstorm.md +3 -3
- package/pi/prompts/close.md +2 -2
- package/pi/prompts/debug.md +3 -3
- package/pi/prompts/discovery.md +3 -3
- package/pi/prompts/dispatcher.md +3 -3
- package/pi/prompts/eli5.md +3 -3
- package/pi/prompts/eval.md +3 -3
- package/pi/prompts/evolve.md +3 -3
- package/pi/prompts/go.md +2 -2
- package/pi/prompts/grill.md +3 -3
- package/pi/prompts/harness-audit.md +2 -3
- package/pi/prompts/memory-cleanup.md +3 -3
- package/pi/prompts/persona-panel.md +3 -3
- package/pi/prompts/plan.md +3 -3
- package/pi/prompts/portfolio.md +2 -2
- package/pi/prompts/reconcile.md +3 -3
- package/pi/prompts/release.md +3 -3
- package/pi/prompts/repo-audit.md +3 -4
- package/pi/prompts/session.md +2 -2
- package/pi/prompts/spinout.md +3 -3
- package/pi/prompts/sunset-review.md +3 -3
- package/pi/prompts/templates-ack.md +1 -1
- package/pi/prompts/test.md +3 -3
- package/pi/prompts/ux-grill.md +3 -3
- package/rules/README.md +1 -1
- package/rules/opt-in-domain/prompt-caching.md +1 -1
- package/rules/opt-in-stack/backend-data.md +1 -1
- package/rules/opt-in-stack/backend.md +3 -3
- package/rules/opt-in-stack/frontend.md +1 -1
- package/rules/opt-in-stack/security-web.md +3 -3
- package/rules/opt-in-stack/swift.md +1 -1
- package/scripts/archive-closed-prds.mjs +2 -2
- package/scripts/auq-audit.mjs +2 -3
- package/scripts/autopilot.mjs +23 -2
- package/scripts/backfill-abandoned-sessions.mjs +171 -15
- package/scripts/backfill-evidence-digest.mjs +2 -1
- package/scripts/backfill-learnings-from-vault.mjs +2 -2
- package/scripts/check-package-manager.mjs +2 -2
- package/scripts/check-sessions-integrity.mjs +300 -0
- package/scripts/ci/assert-vitest-green.mjs +2 -1
- package/scripts/dialectic-deriver.mjs +50 -13
- package/scripts/emit-session.mjs +77 -32
- package/scripts/eval-session.mjs +65 -3
- package/scripts/export-hw-learnings.mjs +2 -1
- package/scripts/express-path.mjs +1 -1
- package/scripts/gc-stale-worktrees.mjs +2 -1
- package/scripts/generate-agents-skills.mjs +102 -29
- package/scripts/generate-codex-skills.mjs +48 -4
- package/scripts/generate-cursor-adapter.mjs +220 -11
- package/scripts/generate-hook-import-set.mjs +12 -27
- package/scripts/generate-pi-prompts.mjs +183 -13
- package/scripts/github-protection-audit.mjs +2 -3
- package/scripts/lib/agent-frontmatter.mjs +23 -1
- package/scripts/lib/agent-status.mjs +2 -31
- package/scripts/lib/auq/clarity.mjs +10 -2
- package/scripts/lib/auq/parse.mjs +12 -31
- package/scripts/lib/auq/schema.mjs +56 -41
- package/scripts/lib/auto-dialectic.mjs +304 -15
- package/scripts/lib/autopilot/flags.mjs +12 -1
- package/scripts/lib/autopilot/kill-switches.mjs +6 -3
- package/scripts/lib/autopilot/loop.mjs +14 -1
- package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
- package/scripts/lib/ci-status-banner.mjs +376 -16
- package/scripts/lib/claude-md-budget-lint.mjs +2 -5
- package/scripts/lib/command-blocker.mjs +408 -33
- package/scripts/lib/config/dialectic.mjs +12 -3
- package/scripts/lib/config/drift-check.mjs +19 -0
- package/scripts/lib/config/gate.mjs +74 -0
- package/scripts/lib/config/reaper.mjs +162 -0
- package/scripts/lib/config.mjs +14 -0
- package/scripts/lib/convergence-monitor.mjs +76 -13
- package/scripts/lib/cursor-hook-bridge.mjs +2 -2
- package/scripts/lib/description-surface.mjs +2 -5
- package/scripts/lib/dispatcher/cli.mjs +2 -1
- package/scripts/lib/ecosystem-health.mjs +11 -0
- package/scripts/lib/ecosystem-wizard.mjs +2 -1
- package/scripts/lib/eval/engine.mjs +421 -53
- package/scripts/lib/eval/judge.mjs +463 -40
- package/scripts/lib/eval/schema.mjs +10 -1
- package/scripts/lib/events-rotation.mjs +221 -25
- package/scripts/lib/events-schema.mjs +114 -0
- package/scripts/lib/events.mjs +524 -5
- package/scripts/lib/fetch-baseline.mjs +3 -8
- package/scripts/lib/frontmatter-guard.mjs +21 -10
- package/scripts/lib/gates/gate-baseline.mjs +27 -2
- package/scripts/lib/gates/gate-full.mjs +28 -3
- package/scripts/lib/gates/gate-helpers.mjs +243 -21
- package/scripts/lib/gates/gate-incremental.mjs +28 -3
- package/scripts/lib/gates/gate-per-file.mjs +27 -2
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
- package/scripts/lib/instruction-budget-guard.mjs +332 -50
- package/scripts/lib/io.mjs +42 -8
- package/scripts/lib/is-main-module.mjs +82 -0
- package/scripts/lib/issue-close-strip-labels.mjs +207 -49
- package/scripts/lib/js-mask.mjs +197 -0
- package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
- package/scripts/lib/locks/index.mjs +32 -25
- package/scripts/lib/maintenance-due-banner.mjs +122 -91
- package/scripts/lib/orphan-reaper.mjs +1588 -0
- package/scripts/lib/peer-cards/merger.mjs +48 -10
- package/scripts/lib/peer-cards/reader.mjs +78 -2
- package/scripts/lib/peer-discovery.mjs +2 -5
- package/scripts/lib/playwright-driver/runner.mjs +2 -1
- package/scripts/lib/process-group.mjs +899 -0
- package/scripts/lib/quality-gate.mjs +107 -28
- package/scripts/lib/reconcile/backlog.mjs +368 -0
- package/scripts/lib/reconcile/engine.mjs +55 -188
- package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
- package/scripts/lib/reconcile/sanitize.mjs +69 -3
- package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
- package/scripts/lib/resource-probe/parsers.mjs +31 -0
- package/scripts/lib/rule-loader.mjs +41 -12
- package/scripts/lib/rules-sync.mjs +2 -5
- package/scripts/lib/scope-echo.mjs +429 -7
- package/scripts/lib/scope-gate.mjs +605 -1
- package/scripts/lib/session-close-backfill.mjs +91 -12
- package/scripts/lib/session-id.mjs +9 -20
- package/scripts/lib/session-invocation.mjs +20 -0
- package/scripts/lib/session-schema/constants.mjs +30 -2
- package/scripts/lib/session-schema/normalizer.mjs +56 -4
- package/scripts/lib/session-schema.mjs +8 -3
- package/scripts/lib/session-start-probes.mjs +95 -10
- package/scripts/lib/sessions-canonical.mjs +23 -0
- package/scripts/lib/sessions-integrity-banner.mjs +7 -1
- package/scripts/lib/sessions-staleness-banner.mjs +193 -51
- package/scripts/lib/skill-evidence-window.mjs +891 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
- package/scripts/lib/skill-evolution/engine.mjs +18 -9
- package/scripts/lib/skill-judge.mjs +45 -3
- package/scripts/lib/state-md.mjs +84 -3
- package/scripts/lib/sunset/walker.mjs +31 -4
- package/scripts/lib/tail-window.mjs +56 -0
- package/scripts/lib/telemetry/schema.mjs +30 -0
- package/scripts/lib/telemetry/sync.mjs +61 -6
- package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
- package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
- package/scripts/lib/tests-src-ratio.mjs +2 -6
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
- package/scripts/lib/user-invocable-skills.mjs +205 -0
- package/scripts/lib/ux-grill/reconcile.mjs +48 -22
- package/scripts/lib/validate/check-agents-skills.mjs +26 -15
- package/scripts/lib/validate/check-banner-parity.mjs +2 -2
- package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
- package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
- package/scripts/lib/validate/check-entry-guard.mjs +329 -0
- package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
- package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
- package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
- package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
- package/scripts/lib/validate/check-rules.mjs +7 -5
- package/scripts/lib/validate/check-skill-links.mjs +35 -6
- package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
- package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
- package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
- package/scripts/lib/validate/check-unwired-features.mjs +220 -33
- package/scripts/lib/validate/check-validator-registration.mjs +36 -12
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
- package/scripts/lib/validate/confidential-names.mjs +10 -0
- package/scripts/lib/validate-vendored-rules.mjs +39 -12
- package/scripts/lib/vault-mirror/namespace.mjs +46 -8
- package/scripts/lib/vault-mirror/process.mjs +10 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
- package/scripts/lib/vault-yaml.mjs +118 -0
- package/scripts/lib/wave-transcript-tail.mjs +2 -2
- package/scripts/lib/worktree/lifecycle.mjs +153 -1
- package/scripts/lock-reaper.mjs +2 -1
- package/scripts/materialize-wave-scope.mjs +87 -4
- package/scripts/migrate-sessions-jsonl.mjs +2 -1
- package/scripts/migrate-vault-paths.mjs +2 -3
- package/scripts/release-session-lock.mjs +305 -0
- package/scripts/release.mjs +109 -39
- package/scripts/relocate-vault-corpus.mjs +2 -3
- package/scripts/repair-invalid-sessions.mjs +2 -2
- package/scripts/resolve-session-invocation.mjs +59 -0
- package/scripts/run-quality-gate.mjs +156 -17
- package/scripts/session-shape.mjs +2 -2
- package/scripts/site-numbers.mjs +35 -11
- package/scripts/sweep-expired-rules.mjs +227 -0
- package/scripts/validate-plugin.mjs +21 -0
- package/scripts/validate-wave-scope.mjs +32 -105
- package/scripts/vault-consolidate.mjs +2 -2
- package/scripts/vault-mirror.mjs +11 -4
- package/scripts/wave-scope-binding.mjs +2 -3
- package/skills/_shared/bootstrap-gate.md +1 -1
- package/skills/_shared/monitor-patterns.md +1 -1
- package/skills/_shared/platform-tools.md +23 -11
- package/skills/_shared/research-evidence.md +53 -0
- package/skills/_shared/state-ownership.md +3 -0
- package/skills/autopilot/SKILL.md +80 -11
- package/skills/bootstrap/SKILL.md +51 -1
- package/skills/brainstorm/SKILL.md +16 -0
- package/skills/claude-md-drift-check/SKILL.md +1 -1
- package/skills/claude-md-drift-check/checker.mjs +49 -11
- package/{commands/close.md → skills/close/SKILL.md} +9 -3
- package/skills/convergence-monitoring/README.md +8 -1
- package/skills/convergence-monitoring/SIGNALS.md +50 -6
- package/skills/convergence-monitoring/SKILL.md +15 -6
- package/skills/debug/SKILL.md +10 -0
- package/skills/discovery/SKILL.md +24 -1
- package/skills/discovery/probes-session.md +2 -2
- package/skills/dispatcher/SKILL.md +38 -7
- package/skills/eli5/SKILL.md +11 -0
- package/skills/eval/SKILL.md +52 -23
- package/skills/eval/rubric-v1.md +1 -0
- package/skills/eval/rubric-v2.md +457 -0
- package/skills/evolve/SKILL.md +9 -2
- package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
- package/skills/gitlab-ops/SKILL.md +3 -2
- package/{commands/go.md → skills/go/SKILL.md} +9 -1
- package/skills/grill/SKILL.md +19 -0
- package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
- package/skills/hook-development/SKILL.md +46 -41
- package/skills/memory-cleanup/SKILL.md +7 -0
- package/skills/npm-publish/SKILL.md +2 -2
- package/skills/persona-panel/SKILL.md +56 -1
- package/skills/persona-panel/persona-format.md +1 -1
- package/skills/plan/SKILL.md +28 -1
- package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
- package/skills/reconcile/SKILL.md +21 -0
- package/{commands/release.md → skills/release/SKILL.md} +16 -2
- package/skills/repo-audit/SKILL.md +7 -0
- package/skills/session-end/SKILL.md +13 -16
- package/skills/session-end/discovery-scan.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +55 -9
- package/skills/session-end/plan-verification.md +2 -2
- package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
- package/skills/session-end/session-metrics-write.md +10 -0
- package/skills/session-plan/SKILL.md +18 -6
- package/skills/session-plan/references/session-plan-task-classification.md +2 -2
- package/skills/session-start/SKILL.md +5 -4
- package/skills/session-start/phase-8-5-express-path.md +6 -6
- package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
- package/skills/spinout/SKILL.md +12 -1
- package/skills/sunset-review/SKILL.md +13 -0
- package/{commands/test.md → skills/test/SKILL.md} +10 -4
- package/skills/ux-grill/SKILL.md +20 -2
- package/skills/wave-executor/SKILL.md +14 -7
- package/skills/wave-executor/circuit-breaker.md +2 -0
- package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
- package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
- package/skills/wave-executor/references/wave-loop-review.md +17 -1
- package/commands/autopilot.md +0 -80
- package/commands/bootstrap.md +0 -56
- package/commands/brainstorm.md +0 -48
- package/commands/debug.md +0 -36
- package/commands/discovery.md +0 -32
- package/commands/dispatcher.md +0 -59
- package/commands/eli5.md +0 -33
- package/commands/eval.md +0 -28
- package/commands/evolve.md +0 -10
- package/commands/grill.md +0 -45
- package/commands/memory-cleanup.md +0 -26
- package/commands/persona-panel.md +0 -121
- package/commands/plan.md +0 -15
- package/commands/reconcile.md +0 -23
- package/commands/repo-audit.md +0 -24
- package/commands/spinout.md +0 -15
- package/commands/sunset-review.md +0 -27
- package/commands/ux-grill.md +0 -51
package/scripts/lock-reaper.mjs
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { reapStaleLocks } from './lib/lock-reaper.mjs';
|
|
25
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Human-readable output
|
|
@@ -175,7 +176,7 @@ EXIT CODES
|
|
|
175
176
|
// CLI guard — prevents execution during test-time imports.
|
|
176
177
|
// ---------------------------------------------------------------------------
|
|
177
178
|
|
|
178
|
-
if (import.meta.url
|
|
179
|
+
if (isMainModule(import.meta.url)) {
|
|
179
180
|
main()
|
|
180
181
|
.then((code) => process.exit(code))
|
|
181
182
|
.catch((err) => {
|
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs';
|
|
26
|
-
import { resolve } from 'node:path';
|
|
27
|
-
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { dirname, resolve } from 'node:path';
|
|
28
27
|
import { writeJsonAtomicSync } from './lib/io.mjs';
|
|
28
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
29
29
|
import { isPeerRecordId } from './lib/scope-gate.mjs';
|
|
30
|
+
import { SCOPE_MATERIALIZED_EVENT, scopeDigest } from './lib/scope-echo.mjs';
|
|
30
31
|
import {
|
|
31
32
|
MANIFEST_SESSION_KEYS,
|
|
32
33
|
manifestSessionBinding,
|
|
@@ -479,6 +480,83 @@ export function materializeWaveScope(records, {
|
|
|
479
480
|
return { aggregatePath, perAgentPaths, removedOrphans: removed, retainedOrphans: retained };
|
|
480
481
|
}
|
|
481
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Is the SEND-side half of the #1092 chain installed on this platform at all?
|
|
485
|
+
*
|
|
486
|
+
* The send-side record only exists because a `PreToolUse` hook with matcher
|
|
487
|
+
* `Agent` observes the dispatch. `hooks-codex.json` / `hooks-cursor.json` /
|
|
488
|
+
* `hooks-pi.json` deliberately carry no such entry (those platforms have no
|
|
489
|
+
* `Agent` dispatch tool — `DOCUMENTED_ASYMMETRIES`), so on them a MISSING
|
|
490
|
+
* dispatch record is not evidence of a missing injection. Recording the answer
|
|
491
|
+
* beside the wave is what lets `--verify` tell those two apart later instead of
|
|
492
|
+
* re-deriving a platform fact from an absence.
|
|
493
|
+
*
|
|
494
|
+
* Fail-CLOSED on any read error: an unreadable hooks file means the guard cannot
|
|
495
|
+
* be shown to be installed, and claiming observability we cannot prove is the
|
|
496
|
+
* direction that produces false `injection-missing` accusations.
|
|
497
|
+
*
|
|
498
|
+
* @param {string} [pluginRoot]
|
|
499
|
+
* @param {typeof readFileSync} [readFile]
|
|
500
|
+
* @returns {boolean}
|
|
501
|
+
*/
|
|
502
|
+
export function transportObservable(pluginRoot = resolve(import.meta.dirname, '..'), readFile = readFileSync) {
|
|
503
|
+
try {
|
|
504
|
+
const parsed = JSON.parse(readFile(resolve(pluginRoot, 'hooks', 'hooks.json'), 'utf8'));
|
|
505
|
+
const entries = parsed?.hooks?.PreToolUse;
|
|
506
|
+
if (!Array.isArray(entries)) return false;
|
|
507
|
+
return entries.some((entry) => typeof entry?.matcher === 'string'
|
|
508
|
+
&& entry.matcher.split('|').map((m) => m.trim()).includes('Agent'));
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* One `orchestrator.wave_dispatch.scope_materialized` record per wave (#1092) —
|
|
516
|
+
* the DEGRADATION half of the chain.
|
|
517
|
+
*
|
|
518
|
+
* It runs here, and not in the hook, precisely because this command runs on
|
|
519
|
+
* EVERY platform while the hook runs on one. Without it a wave with zero
|
|
520
|
+
* `scope_checked` records is ambiguous between "the coordinator skipped the
|
|
521
|
+
* manifest step" (the #1020 failure this whole chain exists for) and "this
|
|
522
|
+
* platform has no dispatch hook".
|
|
523
|
+
*
|
|
524
|
+
* `repoRoot` is the state directory's PARENT, never `process.cwd()`: the state
|
|
525
|
+
* dir is the artefact this command owns, and a caller invoking it from a
|
|
526
|
+
* subdirectory (or a test from the repo root against a `$TMPDIR` state dir)
|
|
527
|
+
* must not write its record into a different repo's ledger.
|
|
528
|
+
*
|
|
529
|
+
* Best-effort in both directions and SILENT on failure: the corpus pins
|
|
530
|
+
* byte-empty stderr on this command's success path
|
|
531
|
+
* (`tests/integration/wave-scope-producer.test.mjs`), and a telemetry write must
|
|
532
|
+
* never be the thing that fails a materialization.
|
|
533
|
+
*
|
|
534
|
+
* @param {{stateDir: string, wave: number, records: Array<{id: string, files: string[]}>}} params
|
|
535
|
+
* @returns {Promise<void>}
|
|
536
|
+
*/
|
|
537
|
+
async function emitScopeMaterialized({ stateDir, wave, records }) {
|
|
538
|
+
try {
|
|
539
|
+
const repoRoot = dirname(resolve(stateDir));
|
|
540
|
+
const agentRecords = records.filter((r) => !isPeerRecordId(r.id));
|
|
541
|
+
const digests = new Set();
|
|
542
|
+
for (const record of agentRecords) {
|
|
543
|
+
if (Array.isArray(record.files) && record.files.length > 0) digests.add(scopeDigest(record.files));
|
|
544
|
+
}
|
|
545
|
+
const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
|
|
546
|
+
await emitEvent(
|
|
547
|
+
SCOPE_MATERIALIZED_EVENT,
|
|
548
|
+
{
|
|
549
|
+
wave,
|
|
550
|
+
agent_count: agentRecords.length,
|
|
551
|
+
digest_count: digests.size,
|
|
552
|
+
transport_observable: transportObservable(),
|
|
553
|
+
...sessionAttribution(repoRoot),
|
|
554
|
+
},
|
|
555
|
+
{ repoRoot },
|
|
556
|
+
);
|
|
557
|
+
} catch { /* observability is best-effort — it never fails a materialization */ }
|
|
558
|
+
}
|
|
559
|
+
|
|
482
560
|
/**
|
|
483
561
|
* @param {string} message
|
|
484
562
|
* @param {number} code
|
|
@@ -536,6 +614,12 @@ export function main() {
|
|
|
536
614
|
? `${JSON.stringify({ ok: true, aggregatePath, perAgentPaths, removedOrphans, retainedOrphans })}\n`
|
|
537
615
|
: `${aggregatePath}\n`,
|
|
538
616
|
);
|
|
617
|
+
|
|
618
|
+
// Fire-and-forget AFTER stdout, so the telemetry append can never reorder or
|
|
619
|
+
// delay this command's own contract. Node drains the pending append before
|
|
620
|
+
// exiting because nothing here calls `process.exit()`; the `.catch` keeps an
|
|
621
|
+
// unhandled rejection from turning observability into an exit code.
|
|
622
|
+
emitScopeMaterialized({ stateDir: args.stateDir, wave: args.wave, records }).catch(() => {});
|
|
539
623
|
} catch (error) {
|
|
540
624
|
if (error instanceof InputError) {
|
|
541
625
|
fail(error.message, 1);
|
|
@@ -549,7 +633,6 @@ export function main() {
|
|
|
549
633
|
}
|
|
550
634
|
}
|
|
551
635
|
|
|
552
|
-
|
|
553
|
-
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
636
|
+
if (isMainModule(import.meta.url)) {
|
|
554
637
|
main();
|
|
555
638
|
}
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
import { readFileSync, writeFileSync, renameSync, copyFileSync, existsSync } from 'node:fs';
|
|
42
42
|
import { join, dirname, basename } from 'node:path';
|
|
43
43
|
import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
|
|
44
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
44
45
|
|
|
45
46
|
const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
|
|
46
47
|
|
|
@@ -440,7 +441,7 @@ async function main() {
|
|
|
440
441
|
|
|
441
442
|
// Run main() only when this file is invoked directly as a CLI, not when imported.
|
|
442
443
|
// Prevents process.exit during test-time imports (#368).
|
|
443
|
-
if (import.meta.url
|
|
444
|
+
if (isMainModule(import.meta.url)) {
|
|
444
445
|
main().catch((err) => {
|
|
445
446
|
process.stderr.write(`migrate-sessions-jsonl: unexpected error: ${err?.stack ?? err}\n`);
|
|
446
447
|
process.exit(2);
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
|
|
51
51
|
import { promises as fs, existsSync } from 'node:fs';
|
|
52
52
|
import { spawnSync } from 'node:child_process';
|
|
53
|
-
import { pathToFileURL } from 'node:url';
|
|
54
53
|
import path from 'node:path';
|
|
55
54
|
import os from 'node:os';
|
|
56
55
|
import {
|
|
@@ -58,6 +57,7 @@ import {
|
|
|
58
57
|
VAULT_MIGRATION_RULES_PATH,
|
|
59
58
|
} from './lib/vault-migration-rules.mjs';
|
|
60
59
|
import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
|
|
60
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
61
61
|
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
63
|
// Constants
|
|
@@ -773,8 +773,7 @@ function emit(opts, rec) {
|
|
|
773
773
|
// importing the module is never a hard error regardless of how the importer runs.
|
|
774
774
|
// ---------------------------------------------------------------------------
|
|
775
775
|
|
|
776
|
-
const invokedDirectly =
|
|
777
|
-
process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
776
|
+
const invokedDirectly =isMainModule(import.meta.url);
|
|
778
777
|
|
|
779
778
|
if (invokedDirectly) {
|
|
780
779
|
main().catch((err) => {
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* release-session-lock.mjs — CLI: release THIS session's `session.lock`, emit the
|
|
3
|
+
* terminal `orchestrator.session.lock.released` breadcrumb, and PROVE the lock is
|
|
4
|
+
* gone before exiting 0.
|
|
5
|
+
*
|
|
6
|
+
* ## Why a CLI and not one more `release()` call site (#1395)
|
|
7
|
+
*
|
|
8
|
+
* Measured 2026-09-19: three of the four `release()` call sites already emit
|
|
9
|
+
* `orchestrator.session.lock.released` — `hooks/on-session-end.mjs`,
|
|
10
|
+
* `scripts/lib/autopilot/worktree-pipeline.mjs` and
|
|
11
|
+
* `scripts/lib/session-transition.mjs`. The silent one was never code at all: it
|
|
12
|
+
* is the PROSE step `skills/session-end/SKILL.md` § Phase 3.8, executed by hand
|
|
13
|
+
* by the coordinator LLM. It deletes the lock, so the SessionEnd hook later reads
|
|
14
|
+
* `status: 'absent'` and emits nothing — this repo recorded 5 `lock.acquired`
|
|
15
|
+
* against 0 `lock.released`. A prose "call X, then check Y" step is the shape
|
|
16
|
+
* that gets skipped (`.claude/rules/process-contracts.md`: 339 of 351 issues
|
|
17
|
+
* closed without the prose-mandated pre-step); the repair is ONE command that
|
|
18
|
+
* verifies its own effect and exits non-zero when it cannot.
|
|
19
|
+
*
|
|
20
|
+
* ## Why the emit is not inside `release()`
|
|
21
|
+
*
|
|
22
|
+
* Three structural reasons, none of them fixable here:
|
|
23
|
+
* 1. Import cycle — `scripts/lib/events.mjs` imports `session-lock.mjs`
|
|
24
|
+
* (`sessionAttribution()` reads the lock), so `session-lock.mjs` cannot
|
|
25
|
+
* import `events.mjs`.
|
|
26
|
+
* 2. `release()` is synchronous; `emitEvent()` is async.
|
|
27
|
+
* 3. After the unlink the event envelope can no longer read the lock for
|
|
28
|
+
* correlation — which is exactly why this CLI captures `session_id` and
|
|
29
|
+
* `semantic_session_id` from the lock BEFORE releasing and passes both
|
|
30
|
+
* EXPLICITLY in the payload.
|
|
31
|
+
*
|
|
32
|
+
* ## Reuse note (BV-001.2)
|
|
33
|
+
*
|
|
34
|
+
* `leaveSourceRoot()` in `scripts/lib/session-transition.mjs` is the closest
|
|
35
|
+
* existing helper and is deliberately NOT reused: it also deregisters the
|
|
36
|
+
* host-wide registry entry, which a session closing in place must NOT do at
|
|
37
|
+
* Phase 3.8 (the SessionEnd hook owns deregistration, at the real end). Its
|
|
38
|
+
* `LOCK_RELEASED_EVENT` constant IS reused, so the event name has one writer.
|
|
39
|
+
*
|
|
40
|
+
* Exit codes: 0 verified · 1 user/input or ownership error · 2 system error.
|
|
41
|
+
*
|
|
42
|
+
* Exports (for tests): main.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { parseArgs } from 'node:util';
|
|
46
|
+
|
|
47
|
+
import { readLockDetailed, loadOwnerProof, release } from './lib/session-lock.mjs';
|
|
48
|
+
import { LOCK_RELEASED_EVENT } from './lib/session-transition.mjs';
|
|
49
|
+
import { emitEvent } from './lib/events.mjs';
|
|
50
|
+
import { isMainModule } from './lib/is-main-module.mjs';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `caller` value stamped into the breadcrumb, so this fourth release site stays
|
|
54
|
+
* distinguishable from `on-session-end`, `session-transition` and the autopilot
|
|
55
|
+
* worktree pipeline in the single `orchestrator.session.lock.*` stream.
|
|
56
|
+
*/
|
|
57
|
+
export const PHASE_CALLER = 'session-end-phase-3-8';
|
|
58
|
+
|
|
59
|
+
const HELP = `release-session-lock.mjs — release this session's lock and prove it is gone
|
|
60
|
+
|
|
61
|
+
USAGE
|
|
62
|
+
node scripts/release-session-lock.mjs --session-id <raw-id> [--repo-root <path>] [--json]
|
|
63
|
+
|
|
64
|
+
WHAT IT DOES
|
|
65
|
+
1. Reads <repo-root>/.orchestrator/session.lock.
|
|
66
|
+
2. Releases it IFF its raw session_id equals --session-id (owner-proof gated,
|
|
67
|
+
exactly as hooks/on-session-end.mjs gates it).
|
|
68
|
+
3. Emits orchestrator.session.lock.released with the ids read BEFORE the
|
|
69
|
+
delete, plus caller="${PHASE_CALLER}".
|
|
70
|
+
4. RE-READS the lock path and exits 0 only when our lock is provably gone.
|
|
71
|
+
|
|
72
|
+
FLAGS
|
|
73
|
+
--session-id <id> REQUIRED. The RAW (physical) session id that owns the lock
|
|
74
|
+
— .orchestrator/session.lock "session_id", never STATE.md
|
|
75
|
+
"session" and never the semantic id. A lock owned by a
|
|
76
|
+
different id is NEVER released.
|
|
77
|
+
--repo-root <path> Repo whose lock is released (default: cwd).
|
|
78
|
+
--json Machine-readable result on stdout.
|
|
79
|
+
-h, --help Show this help and exit 0.
|
|
80
|
+
|
|
81
|
+
OUTCOMES
|
|
82
|
+
absent no lock file — nothing released, NO event, exit 0.
|
|
83
|
+
deleted our lock was removed and re-read as gone.
|
|
84
|
+
already-gone ownership matched but the lock had already vanished; the event
|
|
85
|
+
is still written, because a lock removed by a third party while
|
|
86
|
+
we held it is the forensically interesting case.
|
|
87
|
+
|
|
88
|
+
EXIT CODES
|
|
89
|
+
0 — the lock is provably absent (released now, or nothing to release)
|
|
90
|
+
1 — user/input or ownership error: missing/empty --session-id, unknown flag,
|
|
91
|
+
or a lock owned by a different session (nothing is touched)
|
|
92
|
+
2 — system error: unreadable/corrupt lock, filesystem error, owner-proof
|
|
93
|
+
mismatch, the lock still present after release, or the breadcrumb could
|
|
94
|
+
not be written
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Print the one-line human summary (stdout = data, stderr = diagnostics).
|
|
99
|
+
*
|
|
100
|
+
* @param {{outcome: string, verified: boolean, session_id: string, event_emitted: boolean}} result
|
|
101
|
+
*/
|
|
102
|
+
function printHuman(result) {
|
|
103
|
+
if (result.outcome === 'absent') {
|
|
104
|
+
process.stdout.write('session-lock: nothing to release — no lock file (no event written).\n');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const verified = result.verified ? 'verified gone' : 'NOT verified — lock still present';
|
|
108
|
+
const event = result.event_emitted ? 'breadcrumb written' : 'breadcrumb FAILED';
|
|
109
|
+
process.stdout.write(
|
|
110
|
+
`session-lock ${result.outcome} for session ${result.session_id} — ${verified}, ${event}.\n`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Did OUR lock really vanish?
|
|
116
|
+
*
|
|
117
|
+
* Computed from a FRESH read after `release()` returned, deliberately not from
|
|
118
|
+
* `release()`'s own `verified` field: that field is absent on the benign
|
|
119
|
+
* `no-lock` branch (so a caller copying it reports `verified: false` for a lock
|
|
120
|
+
* that IS gone), and it was computed before this process did anything else. The
|
|
121
|
+
* CLI's whole contract is "exit 0 proves absence", so it re-reads.
|
|
122
|
+
*
|
|
123
|
+
* A FOREIGN lock present at the re-read counts as verified — ours is gone and a
|
|
124
|
+
* sibling legitimately acquired the path in the race window (same rule as
|
|
125
|
+
* `release()`'s post-delete verify; PSA-005: never touch a lock we do not own).
|
|
126
|
+
*
|
|
127
|
+
* @param {string} repoRoot
|
|
128
|
+
* @param {string} sessionId
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
function ourLockIsGone(repoRoot, sessionId) {
|
|
132
|
+
const after = readLockDetailed({ repoRoot });
|
|
133
|
+
if (after.status === 'absent') return true;
|
|
134
|
+
if (after.status === 'ok') return after.lock.session_id !== sessionId;
|
|
135
|
+
// unreadable / corrupt — absence cannot be confirmed, so it is not claimed.
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* CLI entry point.
|
|
141
|
+
*
|
|
142
|
+
* @param {{argv?: string[]}} [opts]
|
|
143
|
+
* @returns {Promise<number>} process exit code
|
|
144
|
+
*/
|
|
145
|
+
export async function main({ argv = process.argv.slice(2) } = {}) {
|
|
146
|
+
let values;
|
|
147
|
+
try {
|
|
148
|
+
({ values } = parseArgs({
|
|
149
|
+
args: argv,
|
|
150
|
+
options: {
|
|
151
|
+
'repo-root': { type: 'string' },
|
|
152
|
+
'session-id': { type: 'string' },
|
|
153
|
+
json: { type: 'boolean', default: false },
|
|
154
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
155
|
+
},
|
|
156
|
+
strict: true,
|
|
157
|
+
allowPositionals: false,
|
|
158
|
+
}));
|
|
159
|
+
} catch (err) {
|
|
160
|
+
process.stderr.write(`release-session-lock: ${err?.message ?? 'bad arguments'}\n`);
|
|
161
|
+
process.stderr.write('Run with --help for usage.\n');
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (values.help) {
|
|
166
|
+
process.stdout.write(HELP);
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const sessionId = values['session-id'];
|
|
171
|
+
if (typeof sessionId !== 'string' || sessionId.length === 0) {
|
|
172
|
+
process.stderr.write('release-session-lock: --session-id <raw-id> is required.\n');
|
|
173
|
+
process.stderr.write('Run with --help for usage.\n');
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const repoRoot = values['repo-root'] ?? process.cwd();
|
|
178
|
+
|
|
179
|
+
const emitResult = async (result) => {
|
|
180
|
+
if (values.json) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
181
|
+
else printHuman(result);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// -- Step 1: read the lock, and capture the ids BEFORE anything is deleted --
|
|
185
|
+
const lockDetail = readLockDetailed({ repoRoot });
|
|
186
|
+
|
|
187
|
+
if (lockDetail.status === 'absent') {
|
|
188
|
+
// Nothing was released, so nothing terminal happened: NO event. Writing one
|
|
189
|
+
// here would put a fictional lifecycle end into the stream — the idempotent
|
|
190
|
+
// re-run case is not a release.
|
|
191
|
+
const result = { ok: true, outcome: 'absent', verified: true, session_id: sessionId, event_emitted: false };
|
|
192
|
+
await emitResult(result);
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (lockDetail.status !== 'ok') {
|
|
197
|
+
process.stderr.write(`release-session-lock: lock is ${lockDetail.status} — ownership cannot be established; nothing touched.\n`);
|
|
198
|
+
await emitResult({
|
|
199
|
+
ok: false, outcome: `lock-${lockDetail.status}`, verified: false, session_id: sessionId, event_emitted: false,
|
|
200
|
+
});
|
|
201
|
+
return 2;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const lock = lockDetail.lock;
|
|
205
|
+
if (lock.session_id !== sessionId) {
|
|
206
|
+
// A raw-id mismatch never grants release ownership (same gate as
|
|
207
|
+
// hooks/on-session-end.mjs). Reconciliation of a foreign lease is the
|
|
208
|
+
// reaper's job, not this command's.
|
|
209
|
+
process.stderr.write(`release-session-lock: lock is owned by a different session — not released.\n`);
|
|
210
|
+
await emitResult({
|
|
211
|
+
ok: false, outcome: 'session-mismatch', verified: false, session_id: sessionId, event_emitted: false,
|
|
212
|
+
});
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const semanticSessionId = typeof lock.semantic_session_id === 'string' && lock.semantic_session_id.length > 0
|
|
217
|
+
? lock.semantic_session_id
|
|
218
|
+
: null;
|
|
219
|
+
|
|
220
|
+
// -- Step 2: release, owner-proof gated (#987/#989) ------------------------
|
|
221
|
+
// `proof` is null whenever ownership cannot be proven; release() treats null
|
|
222
|
+
// as absent and degrades to the session_id-only path, so it is passed
|
|
223
|
+
// through unguarded.
|
|
224
|
+
const proof = loadOwnerProof({ repoRoot });
|
|
225
|
+
const releaseResult = release({ sessionId, repoRoot, proof });
|
|
226
|
+
|
|
227
|
+
if (releaseResult.ok !== true) {
|
|
228
|
+
process.stderr.write(`release-session-lock: release failed — ${releaseResult.reason ?? 'fs-error'}\n`);
|
|
229
|
+
await emitResult({
|
|
230
|
+
ok: false, outcome: `release-${releaseResult.reason ?? 'fs-error'}`, verified: false, session_id: sessionId, event_emitted: false,
|
|
231
|
+
});
|
|
232
|
+
return 2;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const alreadyGone = releaseResult.deleted !== true && releaseResult.reason === 'no-lock';
|
|
236
|
+
if (releaseResult.deleted !== true && !alreadyGone) {
|
|
237
|
+
// 'proof-mismatch' (and any future non-delete reason): ownership matched on
|
|
238
|
+
// the raw id but the second factor contradicted it. Contradictory on-disk
|
|
239
|
+
// state, not a bad argument — exit 2.
|
|
240
|
+
process.stderr.write(`release-session-lock: lock not released — ${releaseResult.reason ?? 'not-deleted'}\n`);
|
|
241
|
+
await emitResult({
|
|
242
|
+
ok: false, outcome: `release-${releaseResult.reason ?? 'not-deleted'}`, verified: false, session_id: sessionId, event_emitted: false,
|
|
243
|
+
});
|
|
244
|
+
return 2;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// -- Step 3: prove it ------------------------------------------------------
|
|
248
|
+
const verified = ourLockIsGone(repoRoot, sessionId);
|
|
249
|
+
const outcome = alreadyGone ? 'already-gone' : 'deleted';
|
|
250
|
+
|
|
251
|
+
// -- Step 4: the terminal breadcrumb --------------------------------------
|
|
252
|
+
// The ids are passed EXPLICITLY: the lock is gone by now, so emitEvent's
|
|
253
|
+
// lock-derived correlation would find nothing to attribute this record to.
|
|
254
|
+
let eventEmitted = true;
|
|
255
|
+
let emitError = null;
|
|
256
|
+
try {
|
|
257
|
+
await emitEvent(LOCK_RELEASED_EVENT, {
|
|
258
|
+
session_id: sessionId,
|
|
259
|
+
...(semanticSessionId !== null ? { semantic_session_id: semanticSessionId } : {}),
|
|
260
|
+
caller: PHASE_CALLER,
|
|
261
|
+
outcome,
|
|
262
|
+
verified,
|
|
263
|
+
}, { repoRoot });
|
|
264
|
+
} catch (err) {
|
|
265
|
+
eventEmitted = false;
|
|
266
|
+
emitError = err?.message ?? String(err);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const result = {
|
|
270
|
+
ok: verified && eventEmitted,
|
|
271
|
+
outcome,
|
|
272
|
+
verified,
|
|
273
|
+
session_id: sessionId,
|
|
274
|
+
...(semanticSessionId !== null ? { semantic_session_id: semanticSessionId } : {}),
|
|
275
|
+
event_emitted: eventEmitted,
|
|
276
|
+
...(emitError !== null ? { event_error: emitError } : {}),
|
|
277
|
+
};
|
|
278
|
+
await emitResult(result);
|
|
279
|
+
|
|
280
|
+
if (!verified) {
|
|
281
|
+
process.stderr.write('release-session-lock: the lock is STILL present after release — not verified.\n');
|
|
282
|
+
return 2;
|
|
283
|
+
}
|
|
284
|
+
if (!eventEmitted) {
|
|
285
|
+
// The release itself succeeded; what failed is the one thing this command
|
|
286
|
+
// exists to guarantee, so it is reported as a system error rather than
|
|
287
|
+
// swallowed. Phase 3.8 treats a non-zero exit as a WARN and continues.
|
|
288
|
+
process.stderr.write(`release-session-lock: breadcrumb could not be written — ${emitError}\n`);
|
|
289
|
+
return 2;
|
|
290
|
+
}
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// CLI guard — prevents execution during test-time imports.
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
if (isMainModule(import.meta.url)) {
|
|
299
|
+
main()
|
|
300
|
+
.then((code) => process.exit(code))
|
|
301
|
+
.catch((err) => {
|
|
302
|
+
process.stderr.write(`release-session-lock: unexpected error: ${err?.stack ?? err}\n`);
|
|
303
|
+
process.exit(2);
|
|
304
|
+
});
|
|
305
|
+
}
|