session-orchestrator 3.21.0 → 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +11 -2
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +10 -10
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +269 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/memory-proposal-collector.md +6 -4
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/eli5.md +33 -0
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/commands/release.md +5 -3
- package/commands/test.md +2 -2
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +7 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +52 -5
- package/docs/session-config-reference.md +57 -12
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +26 -16
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +12 -2
- package/hooks/on-session-end.mjs +237 -104
- package/hooks/on-session-start.mjs +404 -110
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +133 -21
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +12 -8
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +221 -68
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +84 -11
- package/scripts/lib/gates/gate-helpers.mjs +93 -21
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/io.mjs +239 -0
- package/scripts/lib/issue-budget.mjs +284 -31
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +104 -4
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +165 -7
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +111 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +467 -72
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +59 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/vcs-repo-spec.mjs +182 -13
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +478 -0
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/release.mjs +443 -122
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +6 -0
- package/scripts/validate-wave-scope.mjs +73 -1
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +31 -25
- package/skills/_shared/parallel-aware-preamble.md +35 -4
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +61 -8
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +5 -4
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/discovery/SKILL.md +28 -24
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/gitlab-ops/SKILL.md +30 -26
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/npm-publish/SKILL.md +4 -4
- package/skills/peekaboo-driver/SKILL.md +18 -6
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +35 -23
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +44 -4
- package/skills/session-end/SKILL.md +70 -24
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +48 -5
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +96 -48
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/session-start/soul.md +110 -0
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +129 -24
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/templates/_shared/journey-manifest.md +110 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -36,6 +36,11 @@ import { acquire, release, buildLockOwnerProof } from '../session-lock.mjs';
|
|
|
36
36
|
import { emitEvent } from '../events.mjs';
|
|
37
37
|
import { main as gcMain } from '../../gc-stale-worktrees.mjs';
|
|
38
38
|
import { SEMANTIC_ID_RE } from '../session-id.mjs';
|
|
39
|
+
import { repoPathHash } from '../session-registry.mjs';
|
|
40
|
+
// Marker location is owned by its READER (session-end Phase 4a) — importing the
|
|
41
|
+
// constant from there keeps writer and reader on one string. The dependency runs
|
|
42
|
+
// heavy→lean (this pipeline → the dependency-free cleanup helper), never back.
|
|
43
|
+
import { PROMOTION_MARKER_RELPATH } from '../session-end/worktree-cleanup.mjs';
|
|
39
44
|
|
|
40
45
|
// ---------------------------------------------------------------------------
|
|
41
46
|
// Type definitions
|
|
@@ -547,6 +552,134 @@ export async function runStoryPipeline(context, opts = {}) {
|
|
|
547
552
|
/** Valid git branch character set — mirrors isValidBranch from session-id.mjs. */
|
|
548
553
|
const ENTER_WORKTREE_BRANCH_RE = /^[a-zA-Z0-9._/-]+$/;
|
|
549
554
|
|
|
555
|
+
/**
|
|
556
|
+
* Raised when the promotion branch `so/<sessionId>` is already checked out by
|
|
557
|
+
* another worktree. Refusing is the point: adopting a foreign target worktree
|
|
558
|
+
* would put two sessions on one branch (PSA-002 territory), so the caller gets
|
|
559
|
+
* the conflicting path by name instead of a silent takeover.
|
|
560
|
+
*/
|
|
561
|
+
export class WorktreePromotionBranchError extends Error {
|
|
562
|
+
/**
|
|
563
|
+
* @param {string} message
|
|
564
|
+
* @param {{branch: string, checkedOutAt: string}} detail
|
|
565
|
+
*/
|
|
566
|
+
constructor(message, { branch, checkedOutAt } = {}) {
|
|
567
|
+
super(message);
|
|
568
|
+
this.name = 'WorktreePromotionBranchError';
|
|
569
|
+
this.branch = branch;
|
|
570
|
+
this.checkedOutAt = checkedOutAt;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Map of branch name → worktree path for every branch currently checked out by
|
|
576
|
+
* ANY worktree of `repoRoot` (#1067).
|
|
577
|
+
*
|
|
578
|
+
* Deliberately NOT `listWorktrees()` from `../worktree/listing.mjs`: that helper
|
|
579
|
+
* runs `git worktree list --porcelain` in `process.cwd()` (it has no repo
|
|
580
|
+
* parameter), and `enterWorktree` takes `repoRoot` explicitly precisely to avoid
|
|
581
|
+
* CWD drift (#219) — so reusing it would query whichever repo the process
|
|
582
|
+
* happens to sit in. Its `$` seam also has a different shape (`$({cwd})` returns
|
|
583
|
+
* the tag) than `enterWorktree`'s (`exec` IS the tag), so the two cannot share
|
|
584
|
+
* one injected executor. Only the `branch` lines are parsed here; `HEAD`,
|
|
585
|
+
* `detached`, `bare` and `locked` records carry nothing this decision needs.
|
|
586
|
+
*
|
|
587
|
+
* @param {Function} exec zx-like template-tag executor (the DI seam).
|
|
588
|
+
* @param {string} repoRoot Absolute path to the source git repository.
|
|
589
|
+
* @returns {Promise<Map<string, string>>} Branch names (without the
|
|
590
|
+
* `refs/heads/` prefix) → the worktree path that has them checked out.
|
|
591
|
+
* Empty on git failure — the caller then behaves exactly as it did before this
|
|
592
|
+
* check existed, so a broken listing degrades to git's own loud
|
|
593
|
+
* `already used by worktree` error rather than to a silent wrong branch.
|
|
594
|
+
*/
|
|
595
|
+
async function listCheckedOutBranches(exec, repoRoot) {
|
|
596
|
+
let stdout;
|
|
597
|
+
try {
|
|
598
|
+
const result = await exec`git -C ${repoRoot} worktree list --porcelain`;
|
|
599
|
+
stdout = String(result?.stdout ?? '');
|
|
600
|
+
} catch {
|
|
601
|
+
return new Map();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const branches = new Map();
|
|
605
|
+
let currentPath = null;
|
|
606
|
+
for (const rawLine of stdout.split('\n')) {
|
|
607
|
+
const line = rawLine.trimEnd();
|
|
608
|
+
const wtMatch = /^worktree (.+)$/.exec(line);
|
|
609
|
+
if (wtMatch) {
|
|
610
|
+
currentPath = wtMatch[1];
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
const branchMatch = /^branch refs\/heads\/(.+)$/.exec(line);
|
|
614
|
+
if (branchMatch) branches.set(branchMatch[1], currentPath ?? '');
|
|
615
|
+
}
|
|
616
|
+
return branches;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Does `<ref>` resolve in `repoRoot`? Wraps `git rev-parse --verify --quiet`.
|
|
621
|
+
*
|
|
622
|
+
* @param {Function} exec
|
|
623
|
+
* @param {string} repoRoot
|
|
624
|
+
* @param {string} ref
|
|
625
|
+
* @returns {Promise<boolean>} false on any git failure (the ref is absent, or
|
|
626
|
+
* git could not be asked — both lead to the create-new path, whose failure
|
|
627
|
+
* mode is git's own loud error rather than a silent wrong checkout).
|
|
628
|
+
*/
|
|
629
|
+
async function refExists(exec, repoRoot, ref) {
|
|
630
|
+
try {
|
|
631
|
+
await exec`git -C ${repoRoot} rev-parse --verify --quiet ${ref}`;
|
|
632
|
+
return true;
|
|
633
|
+
} catch {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Record, inside the freshly created worktree, that it was auto-promoted —
|
|
640
|
+
* and from where. Best-effort: a failure to write the marker degrades Phase 4a
|
|
641
|
+
* detection to the legacy basename key, it never fails the promotion itself.
|
|
642
|
+
*
|
|
643
|
+
* No absolute source path is stored: `source_root_hash` is `repoPathHash()`, the
|
|
644
|
+
* same stable SHA-256 the session registry uses to correlate repos without
|
|
645
|
+
* exposing the operator's filesystem layout.
|
|
646
|
+
*
|
|
647
|
+
* @param {object} params
|
|
648
|
+
* @param {string} params.wtPath Freshly created worktree.
|
|
649
|
+
* @param {string} params.sourceRoot Resolved source checkout.
|
|
650
|
+
* @param {string} params.sessionId Session label the worktree was created for.
|
|
651
|
+
* @param {string} params.branch Branch the worktree actually landed on.
|
|
652
|
+
* @returns {boolean} true when the marker was written.
|
|
653
|
+
*/
|
|
654
|
+
function writePromotionMarker({ wtPath, sourceRoot, sessionId, branch }) {
|
|
655
|
+
try {
|
|
656
|
+
const markerPath = path.join(wtPath, PROMOTION_MARKER_RELPATH);
|
|
657
|
+
fs.mkdirSync(path.dirname(markerPath), { recursive: true });
|
|
658
|
+
fs.writeFileSync(
|
|
659
|
+
markerPath,
|
|
660
|
+
JSON.stringify(
|
|
661
|
+
{
|
|
662
|
+
source_root_hash: repoPathHash(sourceRoot),
|
|
663
|
+
source_root_basename: path.basename(sourceRoot),
|
|
664
|
+
source_session_id: sessionId,
|
|
665
|
+
branch,
|
|
666
|
+
promoted_at: new Date().toISOString(),
|
|
667
|
+
},
|
|
668
|
+
null,
|
|
669
|
+
2,
|
|
670
|
+
) + '\n',
|
|
671
|
+
'utf8',
|
|
672
|
+
);
|
|
673
|
+
return true;
|
|
674
|
+
} catch (err) {
|
|
675
|
+
console.warn(
|
|
676
|
+
`enterWorktree: could not write promotion marker in ${wtPath} (${err.message}) — ` +
|
|
677
|
+
'session-end Phase 4a falls back to basename detection',
|
|
678
|
+
);
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
550
683
|
/**
|
|
551
684
|
* Create a sibling git worktree for Worktree-Auto-Promotion (#574, Epic #568 P3.1).
|
|
552
685
|
*
|
|
@@ -573,10 +706,25 @@ const ENTER_WORKTREE_BRANCH_RE = /^[a-zA-Z0-9._/-]+$/;
|
|
|
573
706
|
* check as `setupWorktree` (CWE-23 / SEC-013 defence-in-depth). Throws
|
|
574
707
|
* `WorktreeBoundaryError` if the computed path escapes `basePath`.
|
|
575
708
|
*
|
|
576
|
-
* Branch handling
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
709
|
+
* Branch handling — three cases, decided in this order (#1067):
|
|
710
|
+
* 1. `<branch>` is already checked out by ANY worktree of `repoRoot` (the
|
|
711
|
+
* normal session-start Phase 0.5 case, where `branch` is the CURRENT HEAD):
|
|
712
|
+
* git refuses a second checkout of the same branch
|
|
713
|
+
* (`fatal: '<branch>' is already used by worktree at '<repoRoot>'`). Treat
|
|
714
|
+
* `<branch>` as a START POINT only and create a fresh promotion branch
|
|
715
|
+
* `so/<sessionId>` at it: `git worktree add -b so/<sessionId> <wtPath> <branch>`.
|
|
716
|
+
* The source branch and the source worktree are left untouched.
|
|
717
|
+
* If `so/<sessionId>` ALREADY exists (a worktree directory was removed but
|
|
718
|
+
* its branch survived), it is REUSED — `git worktree add <wtPath>
|
|
719
|
+
* so/<sessionId>`, `reusedBranch: true` in the return — because `-b` would
|
|
720
|
+
* abort with `fatal: a branch named 'so/<sessionId>' already exists`.
|
|
721
|
+
* If it exists AND is checked out by some worktree, this throws
|
|
722
|
+
* `WorktreePromotionBranchError` naming that path: adopting a foreign
|
|
723
|
+
* target worktree is refused, never silently taken over.
|
|
724
|
+
* 2. `<branch>` exists but is not checked out (verified via
|
|
725
|
+
* `git rev-parse --verify <branch>`): `git worktree add <wtPath> <branch>`.
|
|
726
|
+
* 3. `<branch>` does not exist: `git worktree add -b <branch> <wtPath>`.
|
|
727
|
+
* Cases 2/3 differ from `setupWorktree`, which always passes `-b`.
|
|
580
728
|
*
|
|
581
729
|
* @param {object} params
|
|
582
730
|
* @param {string} params.basePath - Parent directory where the new worktree goes (absolute).
|
|
@@ -585,9 +733,22 @@ const ENTER_WORKTREE_BRANCH_RE = /^[a-zA-Z0-9._/-]+$/;
|
|
|
585
733
|
* @param {string} params.repoRoot - Path to the source git repository (passed explicitly to avoid CWD drift per #219).
|
|
586
734
|
* @param {object} [opts]
|
|
587
735
|
* @param {Function} [opts.$] - zx-like template-tag executor (DI seam); falls back to lazy `await import('zx')`.
|
|
588
|
-
*
|
|
736
|
+
* Every freshly created worktree also gets a `.orchestrator/promoted-from.json`
|
|
737
|
+
* marker (see {@link PROMOTION_MARKER_RELPATH}) so session-end Phase 4a can
|
|
738
|
+
* still recognise it after the #1069 process boundary hands the worktree to a
|
|
739
|
+
* session with a different id. Marker writing is best-effort and never fails
|
|
740
|
+
* the promotion.
|
|
741
|
+
*
|
|
742
|
+
* @returns {Promise<{ wtPath: string, reused: boolean, branch?: string, promotedFrom?: string, reusedBranch?: true }>}
|
|
743
|
+
* `branch` is the branch the new worktree actually landed on — equal to the
|
|
744
|
+
* `branch` param except in case 1 above, where it is `so/<sessionId>` and
|
|
745
|
+
* `promotedFrom` carries the requested source branch. `reusedBranch` is
|
|
746
|
+
* present (and `true`) only when an existing `so/<sessionId>` was checked out
|
|
747
|
+
* rather than created. All three are absent on the `reused: true` path (no
|
|
748
|
+
* branch was chosen — the worktree pre-existed).
|
|
589
749
|
* @throws {TypeError} when any required param is missing or fails validation.
|
|
590
750
|
* @throws {WorktreeBoundaryError} when the computed worktree path escapes `basePath`.
|
|
751
|
+
* @throws {WorktreePromotionBranchError} when `so/<sessionId>` is checked out elsewhere.
|
|
591
752
|
*/
|
|
592
753
|
export async function enterWorktree({ basePath, sessionId, branch, repoRoot } = {}, opts = {}) {
|
|
593
754
|
// -------------------------------------------------------------------------
|
|
@@ -686,28 +847,91 @@ export async function enterWorktree({ basePath, sessionId, branch, repoRoot } =
|
|
|
686
847
|
}
|
|
687
848
|
|
|
688
849
|
// -------------------------------------------------------------------------
|
|
689
|
-
// Step 5:
|
|
850
|
+
// Step 5: Is `branch` already checked out elsewhere? (#1067)
|
|
851
|
+
//
|
|
852
|
+
// Phase 0.5 passes the CURRENT HEAD as `branch`, so in the normal promotion
|
|
853
|
+
// case it is checked out by `repoRoot` itself and git would refuse a second
|
|
854
|
+
// checkout. Then `branch` is only a start point and the worktree lands on a
|
|
855
|
+
// fresh `so/<sessionId>`; the source branch/worktree stay untouched.
|
|
690
856
|
// -------------------------------------------------------------------------
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
857
|
+
const checkedOutBranches = await listCheckedOutBranches(exec, repoRoot);
|
|
858
|
+
const promoted = checkedOutBranches.has(branch);
|
|
859
|
+
const targetBranch = promoted ? `so/${sessionId}` : branch;
|
|
860
|
+
|
|
861
|
+
// -------------------------------------------------------------------------
|
|
862
|
+
// Step 6: Decide the `git worktree add` argv.
|
|
863
|
+
//
|
|
864
|
+
// On the promotion path the target is `so/<sessionId>`, which can be in any
|
|
865
|
+
// of THREE states — and only the first was handled before:
|
|
866
|
+
// (a) absent → create it at `<branch>`: `add -b so/<id> <wt> <branch>`
|
|
867
|
+
// (b) exists, free → REUSE it: `add <wt> so/<id>`. This is the survivor
|
|
868
|
+
// of a worktree whose directory was removed while its branch stayed
|
|
869
|
+
// behind (`git worktree remove` does not delete the branch); `-b` would
|
|
870
|
+
// abort Phase 0.5 with `fatal: a branch named 'so/<id>' already exists`.
|
|
871
|
+
// (c) exists, checked out elsewhere → REFUSE. Adopting a foreign target
|
|
872
|
+
// worktree is exactly what Phase 0.5 must not do.
|
|
873
|
+
// -------------------------------------------------------------------------
|
|
874
|
+
let reusedBranch = false;
|
|
875
|
+
if (promoted) {
|
|
876
|
+
const checkedOutAt = checkedOutBranches.get(targetBranch);
|
|
877
|
+
if (checkedOutAt !== undefined) {
|
|
878
|
+
throw new WorktreePromotionBranchError(
|
|
879
|
+
`enterWorktree: promotion branch '${targetBranch}' is already checked out at '${checkedOutAt}' — refusing to adopt a foreign worktree`,
|
|
880
|
+
{ branch: targetBranch, checkedOutAt },
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
reusedBranch = await refExists(exec, repoRoot, `refs/heads/${targetBranch}`);
|
|
697
884
|
}
|
|
698
885
|
|
|
699
|
-
|
|
886
|
+
// A branch that a worktree has checked out exists by construction, so the
|
|
887
|
+
// source-branch rev-parse probe is skipped on the promotion path.
|
|
888
|
+
let branchExists = promoted;
|
|
889
|
+
if (!promoted) {
|
|
890
|
+
try {
|
|
891
|
+
await exec`git -C ${repoRoot} rev-parse --verify ${branch}`;
|
|
892
|
+
branchExists = true;
|
|
893
|
+
} catch {
|
|
894
|
+
// Branch does not exist — fall through to create-new path with `-b`.
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (promoted && reusedBranch) {
|
|
899
|
+
await exec`git -C ${repoRoot} worktree add ${wtPath} ${targetBranch}`;
|
|
900
|
+
} else if (promoted) {
|
|
901
|
+
await exec`git -C ${repoRoot} worktree add -b ${targetBranch} ${wtPath} ${branch}`;
|
|
902
|
+
} else if (branchExists) {
|
|
700
903
|
await exec`git -C ${repoRoot} worktree add ${wtPath} ${branch}`;
|
|
701
904
|
} else {
|
|
702
905
|
await exec`git -C ${repoRoot} worktree add -b ${branch} ${wtPath}`;
|
|
703
906
|
}
|
|
704
907
|
|
|
705
908
|
// -------------------------------------------------------------------------
|
|
706
|
-
// Step
|
|
909
|
+
// Step 6b: Record the promotion FACT inside the new worktree (#1069 boundary).
|
|
910
|
+
// Session-end Phase 4a runs in a session whose id is NOT this one, so nothing
|
|
911
|
+
// in the path or the branch identifies the worktree to it — the marker does.
|
|
912
|
+
// -------------------------------------------------------------------------
|
|
913
|
+
writePromotionMarker({
|
|
914
|
+
wtPath,
|
|
915
|
+
sourceRoot: resolvedRepoRoot,
|
|
916
|
+
sessionId,
|
|
917
|
+
branch: targetBranch,
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// -------------------------------------------------------------------------
|
|
921
|
+
// Step 7: WARN to stderr (PRD §3 P3 Gherkin row-1 + #574 DoD).
|
|
707
922
|
// -------------------------------------------------------------------------
|
|
708
923
|
console.warn(
|
|
709
|
-
|
|
924
|
+
promoted
|
|
925
|
+
? `enterWorktree: created sibling worktree at ${wtPath} (branch=${targetBranch}, promoted from ${branch}, sessionId=${sessionId})`
|
|
926
|
+
: `enterWorktree: created sibling worktree at ${wtPath} (branch=${branch}, sessionId=${sessionId})`,
|
|
710
927
|
);
|
|
711
928
|
|
|
712
|
-
|
|
929
|
+
if (promoted) {
|
|
930
|
+
const result = { wtPath, reused: false, branch: targetBranch, promotedFrom: branch };
|
|
931
|
+
// Present only when it happened, so the common shape stays byte-identical
|
|
932
|
+
// for every existing consumer and strict-equality pin.
|
|
933
|
+
if (reusedBranch) result.reusedBranch = true;
|
|
934
|
+
return result;
|
|
935
|
+
}
|
|
936
|
+
return { wtPath, reused: false, branch };
|
|
713
937
|
}
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* - Synchronous file I/O for STATE.md / sessions.jsonl / bootstrap.lock.
|
|
11
11
|
* The async wrapper exists only because `scanBacklog` is async.
|
|
12
12
|
* - Logging is NOT this helper's job — silent graceful-null on every error.
|
|
13
|
-
* -
|
|
13
|
+
* - Relative paths resolve against `opts.repoRoot` (which itself defaults to
|
|
14
|
+
* process.cwd()); an explicit ABSOLUTE path always wins over the root.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -32,9 +33,16 @@ import { scanBacklog, DEFAULT_BACKLOG_LIMIT } from './backlog-scan.mjs';
|
|
|
32
33
|
* null/[] value to the Signals object.
|
|
33
34
|
*
|
|
34
35
|
* @param {object} [opts]
|
|
35
|
-
* @param {string} [opts.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* @param {string} [opts.repoRoot] — absolute project root every relative
|
|
37
|
+
* path below resolves against, and the root forwarded to `scanBacklog`.
|
|
38
|
+
* Defaults to `process.cwd()`. Without it (#1071) a caller running from a
|
|
39
|
+
* worktree or a subdirectory silently read a DIFFERENT repo's STATE.md and
|
|
40
|
+
* sessions.jsonl than the one it was reporting on — measured as
|
|
41
|
+
* `recentSessions: []` against a checkout holding 245 session records.
|
|
42
|
+
* An explicit absolute `statePath`/`sessionsPath`/`lockPath` still wins.
|
|
43
|
+
* @param {string} [opts.statePath] — defaults to '<repoRoot>/.claude/STATE.md'
|
|
44
|
+
* @param {string} [opts.sessionsPath] — defaults to '<repoRoot>/.orchestrator/metrics/sessions.jsonl'
|
|
45
|
+
* @param {string} [opts.lockPath] — defaults to '<repoRoot>/.orchestrator/bootstrap.lock'
|
|
38
46
|
* @param {Array} [opts.learnings] — pre-surfaced top-N learnings; defaults to []
|
|
39
47
|
* @param {number} [opts.backlogLimit] — passed to scanBacklog; defaults to
|
|
40
48
|
* `DEFAULT_BACKLOG_LIMIT` from backlog-scan.mjs (never a local copy of that
|
|
@@ -46,15 +54,24 @@ import { scanBacklog, DEFAULT_BACKLOG_LIMIT } from './backlog-scan.mjs';
|
|
|
46
54
|
* @returns {Promise<import('./mode-selector.mjs').Signals>}
|
|
47
55
|
*/
|
|
48
56
|
export async function buildLiveSignals(opts = {}) {
|
|
57
|
+
const repoRoot =
|
|
58
|
+
typeof opts.repoRoot === 'string' && opts.repoRoot.length > 0
|
|
59
|
+
? opts.repoRoot
|
|
60
|
+
: process.cwd();
|
|
61
|
+
// `resolve(root, p)` returns `p` unchanged when `p` is absolute — explicit
|
|
62
|
+
// per-file overrides therefore keep precedence over repoRoot.
|
|
49
63
|
const statePath = resolve(
|
|
64
|
+
repoRoot,
|
|
50
65
|
typeof opts.statePath === 'string' ? opts.statePath : '.claude/STATE.md'
|
|
51
66
|
);
|
|
52
67
|
const sessionsPath = resolve(
|
|
68
|
+
repoRoot,
|
|
53
69
|
typeof opts.sessionsPath === 'string'
|
|
54
70
|
? opts.sessionsPath
|
|
55
71
|
: '.orchestrator/metrics/sessions.jsonl'
|
|
56
72
|
);
|
|
57
73
|
const lockPath = resolve(
|
|
74
|
+
repoRoot,
|
|
58
75
|
typeof opts.lockPath === 'string' ? opts.lockPath : '.orchestrator/bootstrap.lock'
|
|
59
76
|
);
|
|
60
77
|
const learnings = Array.isArray(opts.learnings) ? opts.learnings : [];
|
|
@@ -137,7 +154,9 @@ export async function buildLiveSignals(opts = {}) {
|
|
|
137
154
|
let backlog = null;
|
|
138
155
|
|
|
139
156
|
try {
|
|
140
|
-
|
|
157
|
+
// `repoRoot` is forwarded so VCS detection and the `-R` host-pinning spec
|
|
158
|
+
// inside scanBacklog answer about the SAME repo the signals describe.
|
|
159
|
+
backlog = await _scan({ limit: backlogLimit, repoRoot });
|
|
141
160
|
} catch {
|
|
142
161
|
// Branch 6: scanBacklog threw — backlog stays null
|
|
143
162
|
}
|