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
|
@@ -177,6 +177,16 @@ export function inspectConfidentialNames({ namesPath, deps = {} } = {}) {
|
|
|
177
177
|
// Unconfigured → no list, no noise. This is the normal case for public repos
|
|
178
178
|
// and for any host that has not opted into confidential-name scanning.
|
|
179
179
|
if (typeof namesPath !== 'string' || namesPath.trim() === '') {
|
|
180
|
+
// UNREACHABLE from the one production caller today (#1273 P1):
|
|
181
|
+
// `check-owner-leakage.mjs` resolves `namesPath` via `resolveHostPath()`
|
|
182
|
+
// and returns its own early `disabledReason` on an empty/non-string path
|
|
183
|
+
// BEFORE ever calling `inspectConfidentialNames` — so in production this
|
|
184
|
+
// branch never runs. It is reachable only for a direct caller (or a test)
|
|
185
|
+
// that skips that pre-filter. Kept regardless: `status` is part of the
|
|
186
|
+
// documented public contract every deep importer may switch on, and
|
|
187
|
+
// 'unconfigured' is the correct answer for exactly this input shape.
|
|
188
|
+
// REVISIT TRIGGER (BV-004): the production caller stops pre-filtering an
|
|
189
|
+
// empty/non-string `namesPath` before calling `inspectConfidentialNames`.
|
|
180
190
|
return { status: 'unconfigured', names: [] };
|
|
181
191
|
}
|
|
182
192
|
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* Catches vendoring bugs that are invisible at the source-of-truth layer but
|
|
7
7
|
* become live footguns once synced into a target repo:
|
|
8
8
|
*
|
|
9
|
-
* - `paths:` frontmatter
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - `paths:` frontmatter in a `rules/` LIBRARY SOURCE — a vendoring-
|
|
10
|
+
* CONVENTION gate, not a loader-compatibility one. Since #795
|
|
11
|
+
* `rule-loader.mjs` accepts `paths:` as an alias for `globs:`, so such a
|
|
12
|
+
* rule IS glob-scoped; `globs:` stays the canonical form for a rule
|
|
13
|
+
* vendored out through the library (#742). See § Scope below — this probe
|
|
14
|
+
* never judges a repo's own consolidated `.claude/rules/` files.
|
|
12
15
|
* - Missing provenance header — `rules-sync.mjs` detects "plugin-owned vs.
|
|
13
16
|
* local override" purely by checking whether the first line starts with
|
|
14
17
|
* `PLUGIN_HEADER_PREFIX`. A source file missing that header gets
|
|
@@ -31,6 +34,25 @@
|
|
|
31
34
|
* module imports the already-exported `parseGlobsFrontmatter` from
|
|
32
35
|
* `rule-loader.mjs` directly.
|
|
33
36
|
*
|
|
37
|
+
* ## Scope: the `rules/` fleet library, never a repo's own `.claude/rules/`
|
|
38
|
+
*
|
|
39
|
+
* Every production caller feeds this module SOURCE files from the plugin's
|
|
40
|
+
* fleet library: `syncRules()` (`rules-sync.mjs`) validates
|
|
41
|
+
* `<pluginRoot>/rules/<relPath>` for each entry listed in `rules/_index.md`
|
|
42
|
+
* BEFORE writing the vendored copy into a consumer's `.claude/rules/` — the
|
|
43
|
+
* written target is never read back through the gate. The CLI's `--dir` is
|
|
44
|
+
* operator-supplied and means that same library.
|
|
45
|
+
*
|
|
46
|
+
* This is load-bearing for `paths-frontmatter`: a repo's own CONSOLIDATED rules
|
|
47
|
+
* under `.claude/rules/` are `paths:`-canonical by design, because Claude
|
|
48
|
+
* Code's native loader reads ONLY `paths:` and treats a rule without it as
|
|
49
|
+
* always-on (`validate/check-rules.mjs` #1108; `docs/rule-authoring.md`
|
|
50
|
+
* § Consolidated rules point 3). Measured 2026-09-16 in this repo: 10
|
|
51
|
+
* path-scoped rule files there, 9 of them `paths:`-only. Pointing `--dir` at
|
|
52
|
+
* such a tree would emit findings that, if obeyed, UNDO that consolidation —
|
|
53
|
+
* which is also why `check-rules.mjs` deliberately does not duplicate this
|
|
54
|
+
* probe for `.claude/rules/`.
|
|
55
|
+
*
|
|
34
56
|
* @module validate-vendored-rules
|
|
35
57
|
*/
|
|
36
58
|
|
|
@@ -40,6 +62,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
40
62
|
import { execFileSync } from 'node:child_process';
|
|
41
63
|
import { createRequire } from 'node:module';
|
|
42
64
|
import { parseGlobsFrontmatter } from './rule-loader.mjs';
|
|
65
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
43
66
|
|
|
44
67
|
// Mirrors rules-sync.mjs's exported PLUGIN_HEADER_PREFIX (rules-sync.mjs
|
|
45
68
|
// line ~13). NOT imported from there on purpose: rules-sync.mjs imports
|
|
@@ -478,10 +501,17 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
|
|
|
478
501
|
// `paths:`-only rule IS glob-scoped. The probe itself stays: it enforces the canonical
|
|
479
502
|
// vendoring form, which is a convention gate, not a loader-compatibility gate. That
|
|
480
503
|
// intent survives #795 and is the subject of the #742 fleet canonicalisation sweep.
|
|
504
|
+
// SCOPE (2026-09-16): this fires only over `rules/` library sources — the module doc's
|
|
505
|
+
// § Scope section names every caller. Do NOT re-point it at a repo's own
|
|
506
|
+
// `.claude/rules/`: those consolidated files are `paths:`-canonical and obeying this
|
|
507
|
+
// remedy there would undo the consolidation (docs/rule-authoring.md point 3).
|
|
481
508
|
message:
|
|
482
509
|
`${relPath}: frontmatter declares a top-level 'paths:' key. It is a recognized alias ` +
|
|
483
510
|
`for 'globs:' (issue #795), so the rule does load glob-scoped — but 'globs:' is the ` +
|
|
484
|
-
`canonical form for
|
|
511
|
+
`canonical form for a rule VENDORED OUT through the plugin's rules/ library, which is ` +
|
|
512
|
+
`this probe's only population. Migrate to 'globs:' (see issue #742). This does NOT ` +
|
|
513
|
+
`apply to a repo's own consolidated .claude/rules/ files — those are paths:-canonical ` +
|
|
514
|
+
`(Claude Code's native loader reads only 'paths:') and this gate never scans them.`,
|
|
485
515
|
line: lineWithinFrontmatter(fm.startLine, fm.body, pathsMatch.index),
|
|
486
516
|
});
|
|
487
517
|
}
|
|
@@ -587,8 +617,9 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
|
|
|
587
617
|
}
|
|
588
618
|
|
|
589
619
|
/**
|
|
590
|
-
* Validates every `*.md` rule file under `dir`, recursively (skips dotfiles
|
|
591
|
-
* and `
|
|
620
|
+
* Validates every `*.md` rule file under `dir`, recursively (skips dotfiles,
|
|
621
|
+
* `_index.md` and `README.md` — neither is a vendored rule: `rules-sync.mjs`
|
|
622
|
+
* vendors only the entries listed in `_index.md`, #1164).
|
|
592
623
|
*
|
|
593
624
|
* When `pluginRoot` is given, every scanned file is additionally passed through
|
|
594
625
|
* `scanVendoringLeaks()` and the findings are collected into the additive
|
|
@@ -612,7 +643,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
|
|
|
612
643
|
const absPath = join(absDir, entry.name);
|
|
613
644
|
if (entry.isDirectory()) {
|
|
614
645
|
files.push(...collectRuleFiles(absPath));
|
|
615
|
-
} else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md') {
|
|
646
|
+
} else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md' && entry.name !== 'README.md') {
|
|
616
647
|
files.push(relative(dir, absPath).replace(/\\/g, '/'));
|
|
617
648
|
}
|
|
618
649
|
}
|
|
@@ -652,11 +683,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
|
|
|
652
683
|
|
|
653
684
|
const __filename = fileURLToPath(import.meta.url);
|
|
654
685
|
|
|
655
|
-
const isMain =
|
|
656
|
-
typeof process !== 'undefined' &&
|
|
657
|
-
process.argv[1] !== null &&
|
|
658
|
-
process.argv[1] !== undefined &&
|
|
659
|
-
resolve(process.argv[1]) === resolve(__filename);
|
|
686
|
+
const isMain =isMainModule(import.meta.url);
|
|
660
687
|
|
|
661
688
|
/**
|
|
662
689
|
* @param {{ ok: boolean, files: Array<{ file: string, violations: RuleViolation[] }>, errorCount: number, warnCount: number }} result
|
|
@@ -5,10 +5,18 @@
|
|
|
5
5
|
* vault writes under `40-learnings/<repoNs>/` and `50-sessions/<repoNs>/`.
|
|
6
6
|
*
|
|
7
7
|
* Contract:
|
|
8
|
-
* resolveRepoNamespace({ vaultName?,
|
|
8
|
+
* resolveRepoNamespace({ vaultName?, repoRoot? }) → string
|
|
9
9
|
*
|
|
10
|
-
* - Pure + deterministic (given the same
|
|
11
|
-
* and the same host-local pseudonym
|
|
10
|
+
* - Pure + deterministic (given the same vaultName, the same `.vault.yaml`
|
|
11
|
+
* under `repoRoot`, the same git remote, and the same host-local pseudonym
|
|
12
|
+
* map).
|
|
13
|
+
* - `repoRoot` (#1389) selects the directory whose `.vault.yaml` is read;
|
|
14
|
+
* it defaults to `process.cwd()`. Named limit: it scopes ONLY that lookup —
|
|
15
|
+
* the {@link deriveRepo} fallback still resolves from the process cwd and
|
|
16
|
+
* is cached per process, so a `repoRoot` that differs from the cwd AND has
|
|
17
|
+
* no declared slug falls back to the cwd's git identity. Every production
|
|
18
|
+
* caller passes its own cwd today; revisit if a caller ever mirrors a repo
|
|
19
|
+
* other than the one it runs in.
|
|
12
20
|
* - Returns a lowercase kebab slug safe for use as a filesystem path segment.
|
|
13
21
|
* - Host-local pseudonym mapping (Epic #725 D5): consulted ONLY at the redaction
|
|
14
22
|
* site (only when a segment is owner-leaky). If such a repo (raw or sanitised)
|
|
@@ -47,6 +55,7 @@ import { isOwnerLeakySegment } from '../../lib/validate/check-owner-leakage.mjs'
|
|
|
47
55
|
import { loadPseudonymMap } from './pseudonym-map.mjs';
|
|
48
56
|
import { loadHostPaths, resolveHostPath } from '../config/host-paths.mjs';
|
|
49
57
|
import { isQueryFailure, resolvePreferredRemote } from '../vcs-repo-spec.mjs';
|
|
58
|
+
import { readVaultSlug } from '../vault-yaml.mjs';
|
|
50
59
|
|
|
51
60
|
// ── Lazy pseudonym-map path resolution (Epic #725 D5) ────────────────────────
|
|
52
61
|
// The map path comes from env SO_NAMESPACE_MAP > owner.yaml paths.namespace-map-path
|
|
@@ -237,19 +246,48 @@ function lookupPseudonym(base, seg) {
|
|
|
237
246
|
*
|
|
238
247
|
* @param {object} [opts]
|
|
239
248
|
* @param {string|null} [opts.vaultName] - Optional override for the repo identifier.
|
|
240
|
-
* When non-empty and non-whitespace, used in place of the git-derived
|
|
241
|
-
* When absent, the
|
|
249
|
+
* When non-empty and non-whitespace, used in place of the declared/git-derived
|
|
250
|
+
* repo name. When absent, the base is the repo's declared `.vault.yaml`
|
|
251
|
+
* `metadata.slug` (#1131), else the git origin via deriveRepo().
|
|
252
|
+
* @param {string|null} [opts.repoRoot] - Directory whose `.vault.yaml` supplies
|
|
253
|
+
* the declared slug (#1389). `undefined`/`null` → `process.cwd()`. Does NOT
|
|
254
|
+
* redirect the deriveRepo() fallback (see the module header's named limit).
|
|
242
255
|
* @returns {string} A single kebab-slug path segment, e.g. 'session-orchestrator'.
|
|
243
256
|
* Special returns:
|
|
244
257
|
* - 'unknown-repo' — slug derivation produced an empty string.
|
|
245
258
|
* - 'redacted-repo' — the raw or slugified value matched an owner-leakage pattern
|
|
246
259
|
* (CP1 personal home path / CP6 private slug / CP10 personal name in Projects path).
|
|
247
260
|
*/
|
|
248
|
-
export function resolveRepoNamespace({ vaultName = null } = {}) {
|
|
249
|
-
// Choose the base identifier: explicit override
|
|
261
|
+
export function resolveRepoNamespace({ vaultName = null, repoRoot = null } = {}) {
|
|
262
|
+
// Choose the base identifier. Precedence: explicit `vaultName` override >
|
|
263
|
+
// the repo's declared `.vault.yaml` `metadata.slug` (#1131) > git-derived.
|
|
264
|
+
//
|
|
265
|
+
// WHY the slug sits HERE and not in deriveRepo(): deriveRepo() is the RAW
|
|
266
|
+
// repo IDENTITY (`org/repo`) and has consumers that want exactly that and
|
|
267
|
+
// nothing else — `vault-relocation-rules.mjs`, `relocate-vault-corpus.mjs`,
|
|
268
|
+
// `named-vault-resolver.mjs` (which documents the `org/repo-name` format).
|
|
269
|
+
// #1131 concerns the vault FOLDER namespace, which is what THIS function
|
|
270
|
+
// decides, so the lookup belongs at this candidate-selection point.
|
|
271
|
+
//
|
|
272
|
+
// Note what this placement does NOT buy: the `source-repo:` frontmatter is
|
|
273
|
+
// this function's output, not deriveRepo()'s (#732; process.mjs deliberately
|
|
274
|
+
// shares ONE value between the write path and the rendered field), so a repo
|
|
275
|
+
// whose declared slug differs from its derived name gets a new `source-repo`
|
|
276
|
+
// either way. That is inherent to moving the folder, not a side effect of
|
|
277
|
+
// where the lookup lives.
|
|
278
|
+
//
|
|
279
|
+
// It sits BELOW `vaultName` (an explicit per-call/operator override must stay
|
|
280
|
+
// honourable) and ABOVE derivation (a declared registration beats a guess from
|
|
281
|
+
// the remote URL or the checkout directory: the remote answers "where does
|
|
282
|
+
// this code live?", the slug answers "which vault project IS this?", and a
|
|
283
|
+
// host-side rename legitimately diverges the two). It sits ABOVE the leak
|
|
284
|
+
// guard below deliberately — a declared slug is operator data flowing into a
|
|
285
|
+
// written path and is checked by CP1/CP6/CP10 and the pseudonym map exactly
|
|
286
|
+
// like any git-derived identifier.
|
|
287
|
+
const declaredSlug = readVaultSlug(repoRoot ?? process.cwd());
|
|
250
288
|
const base = (vaultName && typeof vaultName === 'string' && vaultName.trim())
|
|
251
289
|
? vaultName.trim()
|
|
252
|
-
: deriveRepo();
|
|
290
|
+
: (declaredSlug ?? deriveRepo());
|
|
253
291
|
|
|
254
292
|
// Sanitise: collapse to last path segment, lowercase, strip non-[a-z0-9-].
|
|
255
293
|
const seg = subjectToSlug(base);
|
|
@@ -666,8 +666,12 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
|
|
|
666
666
|
});
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
// #660: namespace new writes under a per-repo subdirectory.
|
|
670
|
-
|
|
669
|
+
// #660: namespace new writes under a per-repo subdirectory. #1389: `ctx.repoRoot`
|
|
670
|
+
// is optional — absent, resolveRepoNamespace falls back to process.cwd().
|
|
671
|
+
const repoNs = resolveRepoNamespace({
|
|
672
|
+
vaultName: ctx?.vaultName ?? null,
|
|
673
|
+
repoRoot: ctx?.repoRoot ?? null,
|
|
674
|
+
});
|
|
671
675
|
// #725 D2: thread the resolved repo namespace into the learning frontmatter as
|
|
672
676
|
// `source-repo` for cross-repo attribution. repoNs is already sanitised +
|
|
673
677
|
// leak-guarded by resolveRepoNamespace, so it is safe to interpolate as-is. The
|
|
@@ -898,7 +902,10 @@ export async function processSession(rawEntry, _lineNum, ctx) {
|
|
|
898
902
|
// owner-leaky repo's real name reached the vault through the session-note
|
|
899
903
|
// frontmatter even though the directory AND the learning `source-repo` field
|
|
900
904
|
// were already pseudonym-mapped/redacted (#732 leak-guard bypass).
|
|
901
|
-
const repoNs = resolveRepoNamespace({
|
|
905
|
+
const repoNs = resolveRepoNamespace({
|
|
906
|
+
vaultName: ctx?.vaultName ?? null,
|
|
907
|
+
repoRoot: ctx?.repoRoot ?? null,
|
|
908
|
+
});
|
|
902
909
|
|
|
903
910
|
// Quality gate (PRD F1.2): skip sessions whose rendered narrative is too short.
|
|
904
911
|
// Measure on the rendered markdown body so the check is schema-agnostic across
|
|
@@ -476,14 +476,24 @@ export function generateSessionNote(entry, options = {}) {
|
|
|
476
476
|
// #1276: lifecycle-only records omit every older count alias. Started
|
|
477
477
|
// measures participation; completed/planned-only counts retain their
|
|
478
478
|
// labels so a plan or a completion count never claims dispatch coverage.
|
|
479
|
-
|
|
480
|
-
|
|
479
|
+
// The `agents_*` triple is the same lifecycle measurement under the
|
|
480
|
+
// de-underscored producer spelling: `metrics-collection.md` documents
|
|
481
|
+
// `agent_count_{planned,started,completed}`, but the live ledger carries
|
|
482
|
+
// one session (`main-2026-09-18-session-1`, 5 waves, measured 2026-09-18
|
|
483
|
+
// over `.orchestrator/metrics/sessions.jsonl`) whose waves emit only
|
|
484
|
+
// `agents_planned` / `agents_started` / `agents_completed` — every one of
|
|
485
|
+
// its 15 counts rendered `?` beside a real number, exactly the #1074 bug
|
|
486
|
+
// class. Each alias sits at the precedence of its documented twin, so no
|
|
487
|
+
// cell that already rendered a value can change.
|
|
488
|
+
const completedAgents = waveCount(w.agent_count_completed) ?? waveCount(w.agents_completed);
|
|
489
|
+
const plannedAgents = waveCount(w.agent_count_planned) ?? waveCount(w.agents_planned);
|
|
481
490
|
const agentsCell =
|
|
482
491
|
waveCount(w.agent_count) ??
|
|
483
492
|
waveCount(w.agents) ??
|
|
484
493
|
waveCount(w.agents_dispatched) ??
|
|
485
494
|
waveCount(w.dispatched) ??
|
|
486
495
|
waveCount(w.agent_count_started) ??
|
|
496
|
+
waveCount(w.agents_started) ??
|
|
487
497
|
(completedAgents === undefined ? undefined : `${completedAgents} completed`) ??
|
|
488
498
|
(plannedAgents === undefined ? MISSING_CELL : `${plannedAgents} planned`);
|
|
489
499
|
const filesCell = waveCount(w.files_changed) ?? waveCount(w.files) ?? MISSING_CELL;
|
|
@@ -43,6 +43,7 @@ import { readConfigFile, parseSessionConfig } from '../config.mjs';
|
|
|
43
43
|
import { validatePathInsideProject } from '../path-utils.mjs';
|
|
44
44
|
import { createSecretValueMasker } from '../secret-masker.mjs';
|
|
45
45
|
import { expandTilde } from '../common.mjs';
|
|
46
|
+
import { readVaultSlug } from '../vault-yaml.mjs';
|
|
46
47
|
|
|
47
48
|
/** Frontmatter sentinel that identifies generator-owned narrative files. */
|
|
48
49
|
export const GENERATOR_MARKER = 'session-orchestrator-vault-status-narrative@1';
|
|
@@ -869,15 +870,29 @@ async function runNarrativeMirror(opts) {
|
|
|
869
870
|
|
|
870
871
|
// Defense-in-depth: when the caller omits (or passes an empty) `repo`, derive
|
|
871
872
|
// it from the operator-configured `vault-name` override (#660/#832) when set,
|
|
872
|
-
// else the
|
|
873
|
-
//
|
|
873
|
+
// else the `.vault.yaml` slug (#1131), else the repoRoot basename — never
|
|
874
|
+
// silently mis-file under 'unknown' (#675 review).
|
|
874
875
|
const vaultNameOverride =
|
|
875
876
|
typeof vaultIntegration['vault-name'] === 'string' && vaultIntegration['vault-name'].trim()
|
|
876
877
|
? vaultIntegration['vault-name'].trim()
|
|
877
878
|
: null;
|
|
878
|
-
const
|
|
879
|
+
const explicitName = (typeof repo === 'string' && repo.trim().length > 0)
|
|
879
880
|
? repo
|
|
880
|
-
: vaultNameOverride
|
|
881
|
+
: vaultNameOverride;
|
|
882
|
+
|
|
883
|
+
// #1131: with NO explicit override, the repo's own `.vault.yaml`
|
|
884
|
+
// `metadata.slug` — the CANONICAL vault registration — outranks the
|
|
885
|
+
// directory basename. Precedence: explicit `repo` opt > `vault-name` >
|
|
886
|
+
// `.vault.yaml` slug > basename.
|
|
887
|
+
//
|
|
888
|
+
// WHY the two overrides still win: both are things a CALLER/operator states
|
|
889
|
+
// for THIS call (`repo` is only ever passed to override the derived name —
|
|
890
|
+
// the sole production caller, `skills/session-end/session-metrics-write.md`,
|
|
891
|
+
// passes `repoRoot` alone), so honouring the file over them would make an
|
|
892
|
+
// explicit override unhonourable. The file wins over the basename, which is
|
|
893
|
+
// a guess nobody stated.
|
|
894
|
+
const vaultYamlSlug = explicitName ? null : readVaultSlug(repoRoot);
|
|
895
|
+
const repoName = explicitName ?? vaultYamlSlug ?? path.basename(path.resolve(repoRoot));
|
|
881
896
|
|
|
882
897
|
const rawVaultDir = vaultIntegration['vault-dir'];
|
|
883
898
|
if (!rawVaultDir || typeof rawVaultDir !== 'string') {
|
|
@@ -886,10 +901,19 @@ async function runNarrativeMirror(opts) {
|
|
|
886
901
|
|
|
887
902
|
const vaultDir = path.resolve(expandTilde(rawVaultDir));
|
|
888
903
|
const candidateSlug = subjectToSlug(repoName) || 'unknown';
|
|
889
|
-
//
|
|
890
|
-
//
|
|
904
|
+
// A declared `.vault.yaml` slug needs no healing — it IS the canonical folder
|
|
905
|
+
// name, so resolveLooseSlug is SKIPPED for it. Running the healer over it
|
|
906
|
+
// would re-open the bug this fixes: a vault that also carries an unhealed
|
|
907
|
+
// legacy folder (e.g. `foobarapp` beside the declared `foo-bar-app`) makes
|
|
908
|
+
// the loose match ambiguous, and ambiguity falls back to the candidate —
|
|
909
|
+
// which is how a duplicate pair, once created, keeps being written to.
|
|
910
|
+
//
|
|
911
|
+
// Otherwise: loose-match against existing 01-projects/ folders before minting
|
|
912
|
+
// a new slug (issue #829 Finding 3) — see resolveLooseSlug for the ambiguity
|
|
891
913
|
// rules. Falls through to `candidateSlug` unchanged on any read failure.
|
|
892
|
-
const repoSlug =
|
|
914
|
+
const repoSlug = vaultYamlSlug
|
|
915
|
+
? candidateSlug
|
|
916
|
+
: resolveLooseSlug(vaultDir, candidateSlug, { readdirSync: injectedFs?.readdirSync });
|
|
893
917
|
const outputPath = resolveNarrativePath(vaultDir, repoSlug);
|
|
894
918
|
|
|
895
919
|
// Defense-in-depth: ensure the resolved file stays inside the vault root.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vault-yaml.mjs — Reader for a repo's `.vault.yaml` registration slug (#1131).
|
|
3
|
+
*
|
|
4
|
+
* A repo registered in the Meta-Vault carries `.vault.yaml` at its root, whose
|
|
5
|
+
* `metadata.slug` is the CANONICAL vault slug for that project — the name of
|
|
6
|
+
* its `01-projects/<slug>/` folder. Until #1131 nothing READ that key: every
|
|
7
|
+
* vault writer derived the slug from the checkout DIRECTORY name instead
|
|
8
|
+
* (`scripts/lib/vault-status/narrative-mirror.mjs` via `subjectToSlug`,
|
|
9
|
+
* `scripts/lib/vault-mirror/namespace.mjs` via `basename(process.cwd())`), so a
|
|
10
|
+
* repo whose directory name differs lexically from its registered slug had its
|
|
11
|
+
* narrative and its learnings/sessions filed under a second, wrong folder — and
|
|
12
|
+
* the loose-slug healer in narrative-mirror (#829) only repairs case and
|
|
13
|
+
* punctuation drift, never a genuinely different name.
|
|
14
|
+
*
|
|
15
|
+
* Exports:
|
|
16
|
+
* VAULT_YAML_FILE — the basename this module reads
|
|
17
|
+
* readVaultSlug — repoRoot → validated `metadata.slug` string, or null
|
|
18
|
+
*
|
|
19
|
+
* Contract: NEVER throws. Absent file, unreadable file, malformed YAML, missing
|
|
20
|
+
* key, non-string value, or a value that fails the repo's slug predicate all
|
|
21
|
+
* return `null`, leaving every caller on its pre-#1131 derivation chain.
|
|
22
|
+
*
|
|
23
|
+
* Import closure is deliberately tiny (`node:fs`, `node:path`, `node:module`,
|
|
24
|
+
* `./vault-mirror/utils.mjs`) — `namespace.mjs` is documented as a leaf-ward
|
|
25
|
+
* module and must stay one.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFileSync } from 'node:fs';
|
|
29
|
+
import path from 'node:path';
|
|
30
|
+
import { createRequire } from 'node:module';
|
|
31
|
+
|
|
32
|
+
import { isValidSlug } from './vault-mirror/utils.mjs';
|
|
33
|
+
|
|
34
|
+
/** Basename of the per-repo vault registration file. */
|
|
35
|
+
export const VAULT_YAML_FILE = '.vault.yaml';
|
|
36
|
+
|
|
37
|
+
// ── Lazy js-yaml resolution ──────────────────────────────────────────────────
|
|
38
|
+
//
|
|
39
|
+
// Same shape (and same reason) as `getYaml()` in scripts/lib/owner-yaml.mjs:
|
|
40
|
+
// a static `import yaml from 'js-yaml'` crashes any consumer running without
|
|
41
|
+
// `node_modules`, at MODULE LOAD, where the failure is not catchable. Both
|
|
42
|
+
// consumers of this reader are synchronous, so `createRequire` — not
|
|
43
|
+
// `await import()` — is the resolution mechanism.
|
|
44
|
+
|
|
45
|
+
/** @type {null | false | { load: Function }} */
|
|
46
|
+
let _yaml = null;
|
|
47
|
+
let _yamlWarned = false;
|
|
48
|
+
|
|
49
|
+
function getYaml() {
|
|
50
|
+
if (_yaml !== null) return _yaml === false ? null : _yaml;
|
|
51
|
+
try {
|
|
52
|
+
_yaml = createRequire(import.meta.url)('js-yaml');
|
|
53
|
+
} catch {
|
|
54
|
+
_yaml = false;
|
|
55
|
+
}
|
|
56
|
+
return _yaml === false ? null : _yaml;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* One-per-process actionable WARN, emitted ONLY when a `.vault.yaml` actually
|
|
61
|
+
* exists and could therefore have changed the resolved slug. A repo without the
|
|
62
|
+
* file loses nothing by the parser being absent, so it stays silent.
|
|
63
|
+
*/
|
|
64
|
+
function warnYamlMissing() {
|
|
65
|
+
if (_yamlWarned) return;
|
|
66
|
+
_yamlWarned = true;
|
|
67
|
+
process.stderr.write(
|
|
68
|
+
"WARN vault-yaml: 'js-yaml' is not installed — .vault.yaml cannot be parsed; " +
|
|
69
|
+
'the vault slug falls back to the directory-derived name (run \'npm install\' in the plugin directory)\n',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read the canonical vault slug declared at `metadata.slug` in
|
|
75
|
+
* `<repoRoot>/.vault.yaml`.
|
|
76
|
+
*
|
|
77
|
+
* The value becomes a FILESYSTEM PATH SEGMENT under the operator's vault, so it
|
|
78
|
+
* is validated with the repo's existing slug predicate ({@link isValidSlug},
|
|
79
|
+
* `/^[a-z0-9]+(?:-[a-z0-9]+)*$/`) rather than trusted. That rejects `/`, `..`,
|
|
80
|
+
* leading dots, whitespace and uppercase — this validation is a trust-boundary
|
|
81
|
+
* control, not hygiene: `.vault.yaml` is repo-supplied data, and a slug such as
|
|
82
|
+
* `../../etc` would otherwise escape the vault root.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} repoRoot - absolute or relative path to the repository root.
|
|
85
|
+
* @returns {string|null} the validated slug, or `null` when it cannot be read.
|
|
86
|
+
*/
|
|
87
|
+
export function readVaultSlug(repoRoot) {
|
|
88
|
+
if (typeof repoRoot !== 'string' || repoRoot.trim() === '') return null;
|
|
89
|
+
|
|
90
|
+
let text;
|
|
91
|
+
try {
|
|
92
|
+
text = readFileSync(path.join(repoRoot, VAULT_YAML_FILE), 'utf8');
|
|
93
|
+
} catch {
|
|
94
|
+
// Absent, unreadable, a directory, … — all "no declared slug".
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const yaml = getYaml();
|
|
99
|
+
if (!yaml) {
|
|
100
|
+
warnYamlMissing();
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let doc;
|
|
105
|
+
try {
|
|
106
|
+
doc = yaml.load(text);
|
|
107
|
+
} catch {
|
|
108
|
+
// Malformed YAML must never break a vault write.
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!doc || typeof doc !== 'object') return null;
|
|
113
|
+
const metadata = doc.metadata;
|
|
114
|
+
if (!metadata || typeof metadata !== 'object') return null;
|
|
115
|
+
|
|
116
|
+
const slug = metadata.slug;
|
|
117
|
+
return isValidSlug(slug) ? slug : null;
|
|
118
|
+
}
|
|
@@ -55,12 +55,12 @@ import {
|
|
|
55
55
|
} from 'node:fs';
|
|
56
56
|
import { homedir } from 'node:os';
|
|
57
57
|
import { join, relative, isAbsolute } from 'node:path';
|
|
58
|
-
import { pathToFileURL } from 'node:url';
|
|
59
58
|
|
|
60
59
|
import { emitEvent, sessionAttribution } from './events.mjs';
|
|
61
60
|
import { tryAcquireFileLock, releaseFileLock } from './file-lock.mjs';
|
|
62
61
|
import { readLock, isLockLive } from './session-lock.mjs';
|
|
63
62
|
import { resolveSubagentSidecar } from '../../hooks/_lib/subagent-paths.mjs';
|
|
63
|
+
import { isMainModule } from './is-main-module.mjs';
|
|
64
64
|
|
|
65
65
|
const DEFAULT_INTERVAL_S = 2;
|
|
66
66
|
const EVENTS_FILE_REL = '.orchestrator/metrics/events.jsonl';
|
|
@@ -998,6 +998,6 @@ function main() {
|
|
|
998
998
|
|
|
999
999
|
// Run only when executed as a script — importing for unit tests must not parse
|
|
1000
1000
|
// vitest's argv and exit 1.
|
|
1001
|
-
if (
|
|
1001
|
+
if (isMainModule(import.meta.url)) {
|
|
1002
1002
|
main();
|
|
1003
1003
|
}
|
|
@@ -46,6 +46,15 @@ async function _exists(p) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The one directory session-orchestrator worktrees may live in.
|
|
51
|
+
* Host-shared: several sessions run concurrently against the same base.
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function _worktreeBaseDir() {
|
|
55
|
+
return path.join(os.tmpdir(), 'so-worktrees');
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
/**
|
|
50
59
|
* Compute the standard branch name and tmp path for a given suffix.
|
|
51
60
|
* @param {string} suffix
|
|
@@ -53,10 +62,146 @@ async function _exists(p) {
|
|
|
53
62
|
*/
|
|
54
63
|
function _worktreeInfo(suffix) {
|
|
55
64
|
const branch = `so-worktree-${suffix}`;
|
|
56
|
-
const wtPath = path.join(
|
|
65
|
+
const wtPath = path.join(_worktreeBaseDir(), branch);
|
|
57
66
|
return { branch, wtPath };
|
|
58
67
|
}
|
|
59
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Resolve symlinks, returning null when the path does not exist.
|
|
71
|
+
* @param {string} p
|
|
72
|
+
* @returns {Promise<string|null>}
|
|
73
|
+
*/
|
|
74
|
+
async function _realpathOrNull(p) {
|
|
75
|
+
try {
|
|
76
|
+
return await fs.realpath(p);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* True when `wtPath` is NOT a live working tree of any repository — i.e. either
|
|
84
|
+
* it carries no `.git` entry at all, or its `.git` file points at an
|
|
85
|
+
* administrative directory that no longer exists (the registration was dropped).
|
|
86
|
+
*
|
|
87
|
+
* A directory whose registration is intact is never an orphan: `git worktree
|
|
88
|
+
* remove --force` handles that case, so this predicate is what keeps a live
|
|
89
|
+
* worktree — including one owned by a PEER session, or by another repository —
|
|
90
|
+
* out of the delete path below.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} wtPath
|
|
93
|
+
* @returns {Promise<boolean>}
|
|
94
|
+
*/
|
|
95
|
+
async function _isUnregisteredWorktreeDir(wtPath) {
|
|
96
|
+
const dotGit = path.join(wtPath, '.git');
|
|
97
|
+
let st;
|
|
98
|
+
try {
|
|
99
|
+
st = await fs.lstat(dotGit);
|
|
100
|
+
} catch {
|
|
101
|
+
return true; // No .git entry — not a working tree of anything.
|
|
102
|
+
}
|
|
103
|
+
// A real .git DIRECTORY means a full repository lives here; never ours to delete.
|
|
104
|
+
if (!st.isFile()) return false;
|
|
105
|
+
|
|
106
|
+
const content = await fs.readFile(dotGit, 'utf8').catch(() => '');
|
|
107
|
+
const match = /^gitdir:\s*(.+)$/m.exec(content);
|
|
108
|
+
if (!match) return false; // Unparseable — treat as live, refuse.
|
|
109
|
+
|
|
110
|
+
const adminDir = path.resolve(wtPath, match[1].trim());
|
|
111
|
+
// Admin dir still present → git knows this worktree → not an orphan.
|
|
112
|
+
return !(await _exists(adminDir));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove an ORPHAN worktree directory (issue #984).
|
|
117
|
+
*
|
|
118
|
+
* An interrupted `removeWorktree` drops the git registration first and can die
|
|
119
|
+
* before the directory is gone. `git worktree add` then fails with
|
|
120
|
+
* "'…' already exists" while `git worktree remove --force` fails with
|
|
121
|
+
* "'…' is not a working tree" (swallowed by nothrow) — the retry loops on the
|
|
122
|
+
* same error until someone deletes the directory by hand.
|
|
123
|
+
*
|
|
124
|
+
* SAFETY — this is a recursive delete inside library code, so every condition
|
|
125
|
+
* below is asserted in code before `fs.rm` runs. Any failed condition returns
|
|
126
|
+
* false and leaves the directory alone (the retry then fails with git's own
|
|
127
|
+
* error, which is the pre-#984 behaviour):
|
|
128
|
+
* 1. `wtPath` itself is a real directory and NOT a symlink — the LEAF is never
|
|
129
|
+
* followed. (A symlink one level up is covered by condition 2.)
|
|
130
|
+
* 2. The literal base `<os.tmpdir()>/so-worktrees` is itself a real directory
|
|
131
|
+
* and NOT a symlink. The base lives on a host-shared /tmp where a co-tenant
|
|
132
|
+
* can pre-create it; without this check the realpath of a planted base
|
|
133
|
+
* symlink BECOMES the delete root, so a `so-worktree-*` directory anywhere
|
|
134
|
+
* on the filesystem would qualify.
|
|
135
|
+
* 3. The REALPATH of `wtPath` is a DIRECT child of the REALPATH of that base
|
|
136
|
+
* — both sides resolved, because macOS resolves /tmp → /private/tmp and
|
|
137
|
+
* os.tmpdir() itself may be a symlinked path.
|
|
138
|
+
* 4. The resolved basename starts with `so-worktree-`.
|
|
139
|
+
* 5. It is not a live working tree of ANY repository (see above).
|
|
140
|
+
* Note what is NOT asserted: an intermediate component of `wtPath` other than
|
|
141
|
+
* the base cannot exist — the path is `<base>/so-worktree-<suffix>`, computed by
|
|
142
|
+
* `_worktreeInfo`, never caller-supplied.
|
|
143
|
+
* `git worktree prune` is deliberately NOT used: it is repo-global and would
|
|
144
|
+
* drop a peer session's stale registration.
|
|
145
|
+
*
|
|
146
|
+
* CEILING (BV-004): the delete is bounded to one directory directly under the
|
|
147
|
+
* fixed base `<os.tmpdir()>/so-worktrees` whose name carries the `so-worktree-`
|
|
148
|
+
* prefix. Revisit when worktree paths become caller-supplied (the
|
|
149
|
+
* `validateWorkspacePath` follow-up noted above) or move out of os.tmpdir() —
|
|
150
|
+
* at that point the containment base is no longer a constant and must be passed
|
|
151
|
+
* in and validated by the caller.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} wtPath
|
|
154
|
+
* @returns {Promise<boolean>} true when a directory was actually removed.
|
|
155
|
+
*/
|
|
156
|
+
async function _removeOrphanWorktreeDir(wtPath) {
|
|
157
|
+
let lst;
|
|
158
|
+
try {
|
|
159
|
+
lst = await fs.lstat(wtPath);
|
|
160
|
+
} catch {
|
|
161
|
+
return false; // Nothing there.
|
|
162
|
+
}
|
|
163
|
+
if (lst.isSymbolicLink() || !lst.isDirectory()) return false;
|
|
164
|
+
|
|
165
|
+
// The containment BASE must be a real directory too: on a host-shared /tmp a
|
|
166
|
+
// co-tenant can pre-create `<os.tmpdir()>/so-worktrees` as a symlink, and a
|
|
167
|
+
// realpath-resolved base would then relocate the delete root off-tmpdir.
|
|
168
|
+
const baseDir = _worktreeBaseDir();
|
|
169
|
+
let baseLst;
|
|
170
|
+
try {
|
|
171
|
+
baseLst = await fs.lstat(baseDir);
|
|
172
|
+
} catch {
|
|
173
|
+
return false; // No base directory — nothing under it to remove.
|
|
174
|
+
}
|
|
175
|
+
if (baseLst.isSymbolicLink() || !baseLst.isDirectory()) return false;
|
|
176
|
+
|
|
177
|
+
const realWt = await _realpathOrNull(wtPath);
|
|
178
|
+
const realBase = await _realpathOrNull(baseDir);
|
|
179
|
+
if (!realWt || !realBase) return false;
|
|
180
|
+
if (!isPathInside(realWt, realBase)) return false;
|
|
181
|
+
// Direct child only — matches the CEILING below; no nested path qualifies.
|
|
182
|
+
if (path.dirname(realWt) !== realBase) return false;
|
|
183
|
+
if (!path.basename(realWt).startsWith('so-worktree-')) return false;
|
|
184
|
+
|
|
185
|
+
if (!(await _isUnregisteredWorktreeDir(realWt))) return false;
|
|
186
|
+
|
|
187
|
+
// RACE WINDOW (accepted, #1390 P7): `_isUnregisteredWorktreeDir` above and
|
|
188
|
+
// `fs.rm` below are two separate syscalls, not one atomic check-then-act.
|
|
189
|
+
// Both plausible racers here are session-orchestrator sessions on the SAME
|
|
190
|
+
// host, contending over the same `<os.tmpdir()>/so-worktrees` base: one
|
|
191
|
+
// could register (`git worktree add`) or delete `realWt` in the gap between
|
|
192
|
+
// the two calls. No lock primitive guards this sub-millisecond window
|
|
193
|
+
// between our own sessions — the worst case is a benign retry (the caller's
|
|
194
|
+
// `git worktree add` above already retries once on failure), never data
|
|
195
|
+
// loss, because neither racer ever touches a path outside its own
|
|
196
|
+
// `so-worktree-*` name.
|
|
197
|
+
// REVISIT TRIGGER (BV-004): an UNRELATED process gains the ability to
|
|
198
|
+
// create or delete `so-worktree-*` directories under this base — at that
|
|
199
|
+
// point the race is no longer bounded to our own retry semantics and this
|
|
200
|
+
// window needs an actual lock, not an accepted gap.
|
|
201
|
+
await fs.rm(realWt, { recursive: true, force: true });
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
60
205
|
// ---------------------------------------------------------------------------
|
|
61
206
|
// validateWorkspacePath
|
|
62
207
|
// ---------------------------------------------------------------------------
|
|
@@ -136,6 +281,13 @@ export async function createWorktree(suffix, baseRef = 'HEAD', options = {}) {
|
|
|
136
281
|
await nothrow(git`git worktree remove ${wtPath} --force`);
|
|
137
282
|
await nothrow(git`git branch -D ${branch}`);
|
|
138
283
|
|
|
284
|
+
// Still on disk after the remove attempt → git has no registration for it
|
|
285
|
+
// (issue #984). Delete the orphan directory under the guards documented on
|
|
286
|
+
// _removeOrphanWorktreeDir; a live/registered worktree never reaches here.
|
|
287
|
+
if (await _exists(wtPath)) {
|
|
288
|
+
await _removeOrphanWorktreeDir(wtPath);
|
|
289
|
+
}
|
|
290
|
+
|
|
139
291
|
try {
|
|
140
292
|
await git`git worktree add -b ${branch} ${wtPath} ${baseRef}`;
|
|
141
293
|
} catch (secondErr) {
|