Skip to content

feat(worktree): add worktree create verb (AB#3096, PR 1b3) - #312

Merged
PolyphonyRequiem merged 1 commit into
mainfrom
feature/worktree-create
May 12, 2026
Merged

feat(worktree): add worktree create verb (AB#3096, PR 1b3)#312
PolyphonyRequiem merged 1 commit into
mainfrom
feature/worktree-create

Conversation

@PolyphonyRequiem

Copy link
Copy Markdown
Owner

Summary

Adds the polyphony worktree create --apex N --branch B [--ref R] verb — the per-item worktree-creation primitive used by every workflow step that needs an impl/, plan/, mg/, or evidence/ worktree under an apex run. Mirrors the structure of worktree init-apex (PR #311 / PR 1b2) with branch-grammar validation, bootstrap-dependency checks, and explicit --ref handling.

Closes AB#3096. Part of AB#3085 (bare-repo + per-run worktree epic).

Stacking

Stacked on PR #311 (PR 1b2: worktree init-apex). Branch feature/worktree-create is off feature/worktree-init-apex tip. The diff against main will be clean once #311 merges; please merge #311 first.

What this PR does

Implements:

polyphony worktree create --apex {N} --branch {B} [--ref {R}]

Behavior:

  1. Validates --apex > 0 and that --branch parses against the canonical grammar (from BranchSlug.TryParse: feature/{r}, plan/{r}[-{item_id}], mg/{r}_{mg_path}, impl/{r}-{item_id}, evidence/{r}-{item_id}).
  2. Refuses feature/{N} branches with unsupported_branch_kindinit-apex is the canonical path for the apex feature worktree.
  3. Asserts parsed.RootId == --apex (else branch_apex_mismatch).
  4. Resolves runs_root and main_path via RunsRootResolver (from PR 1b1).
  5. Asserts the derived target path {runs_root}/apex-{N}/{slug}/ lives under runs_root AND not under main_path via PathBoundary (from PR 1b2). The hijack is structurally impossible.
  6. Asserts the apex was bootstrapped: {runs_root}/apex-{N}/feature-{N} must already be a registered worktree on feature/{N}. Otherwise apex_not_initialized (catches partial-init, missing apex root, manual mkdir).
  7. Applies a deterministic create-or-attach matrix (path checks first, then branch state).

Reason taxonomy

reason When
invalid_apex apex <= 0
invalid_branch grammar-rejected by BranchSlug.TryParse
branch_apex_mismatch parsed root id differs from --apex
unsupported_branch_kind branch is feature/{N} (use init-apex)
common_dir_unavailable git rev-parse --git-common-dir failed
filesystem_failure resolved path violated a boundary invariant
apex_not_initialized {apex_root}/feature-{N} is not a registered worktree on feature/{N}
git_failure git non-zero with no race recovery, or porcelain parse threw
path_exists_wrong_branch target path is a worktree on a different branch
path_exists_not_worktree target path exists (file or dir) but is not registered
branch_in_use local branch already checked out at another worktree
remote_branch_exists local branch missing AND origin/{branch} exists (refused regardless of --ref)
ref_required local branch missing AND --ref empty (after remote check passed)

outcome is one of: created, attached, idempotent, failed.

Rubber-duck-validated decisions

  • Bootstrap dependency is structural (not just Directory.Exists). The check that {apex_root}/feature-{N} is a worktree on feature/{N} is derived from the same git worktree list --porcelain call we already make for path-existence — no extra git invocations. Catches partial-init, manual mkdir, and missing apex root in one signal.
  • Reject feature/{N} branches. BranchSlug.TryParse accepts the Feature kind (and other verbs need that), but create rejects it with unsupported_branch_kind. Otherwise create would become an unprotected back-door around init-apex's remote-branch + always-root-from-main safety.
  • remote_branch_exists always wins, even with --ref. Workflow-rerun safety: if the local branch is missing but origin/{branch} exists, refuse regardless of what was passed for --ref. The operator must explicitly fetch + branch first to avoid silent fork.
  • No cwd-refusal. Path is self-derived from runs_root; cwd has no impact on safety. init-apex legitimately runs from main during bootstrap, and the same logic applies to create.
  • No ref_unresolvable pre-check. Git is the authoritative resolver; a bad ref naturally surfaces as git_failure with stderr.
  • Ref serializes as ref (not ref_spec). ref is valid JSON and doesn't collide with JSON Schema's $ref.
  • Slug derivation: only the leading kind/ separator is replaced. impl/3085-3072impl-3085-3072; mg/3085_pg-foomg-3085_pg-foo (underscores preserved).

Files

  • src/Polyphony/Models/WorktreeCreateResult.cs (new) — result record. Doc-comment is the authoritative reason taxonomy.
  • src/Polyphony/Commands/WorktreeCommands.Create.cs (new) — partial verb. Reuses static helpers from WorktreeCommands.InitApex.cs (FindByPath, NormalizePath, PathExists, GitStderrOrFallback, ProbeIdempotentAsync, ParsePorcelain).
  • src/Polyphony/PolyphonyJsonContext.cs[JsonSerializable(typeof(WorktreeCreateResult))].
  • src/Polyphony/Commands/WorktreeCommands.cs<see cref="Create"/> doc-line.
  • tests/Polyphony.Tests/Commands/WorktreeCommandsCreateTests.cs (new) — 27 tests covering arg validation, branch grammar (incl. feature/N refusal), partial-init detection, path-exists matrix, branch-state matrix (incl. ref_required, remote-refusal-with-ref, attach-without-ref), race tolerance, JSON contract.
  • artifacts/verb-output-schemas.json — regenerated; adds worktree create entry.

What this PR does NOT do

  • Does not invoke worktree create from any workflow YAML or PowerShell helper. That's PR 4 (workflow integration + driver pre-flight).
  • Does not add worktree gc. That's PR 7.
  • Does not change launcher behavior. That's PR 3.
  • Does not change branch invariants documented in polyphony-branch-model.

Validation

  • 27/27 new tests pass in isolation
  • Full Polyphony.Tests suite green (3315 passed; one documented parallel-race flake passed on retry)
  • All 4 CI lints pass locally: prose-children, jinja-resolver, version-drift, conductor-validate
  • Schema artifact regenerated; worktree create entry contains apex/branch/ref inputs and Polyphony.WorktreeCreateResult result type

Adds `polyphony worktree create --apex N --branch B [--ref R]` — the per-item worktree creation primitive used by all workflow steps that need an impl/, plan/, mg/, or evidence/ worktree under an apex run.

Mirrors the structure of `worktree init-apex` (PR #311 / PR 1b2): branch-grammar validation, bootstrap-dependency check, race-tolerant idempotent recovery.

Rubber-duck-validated decisions:

- Bootstrap dependency is structural: `{apex_root}/feature-{N}` must be a registered worktree on `feature/{N}`, not just Directory.Exists. Catches partial-init in one signal.

- Reject `feature/{N}` branches with `unsupported_branch_kind`: init-apex is the canonical path; otherwise create becomes an unprotected back-door.

- `remote_branch_exists` always wins (even with `--ref`): workflow-rerun safety; operator must explicitly fetch+branch first to avoid silent fork.

- No cwd-refusal: path is self-derived from runs_root; cwd has no impact on safety.

Stacked on PR #311. Branch off `feature/worktree-init-apex` tip; rebase onto main once #311 merges.

Part of AB#3085 (bare-repo + per-run worktree epic).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PolyphonyRequiem
PolyphonyRequiem force-pushed the feature/worktree-create branch from 63e4b18 to da9f782 Compare May 12, 2026 01:11
PolyphonyRequiem pushed a commit that referenced this pull request May 12, 2026
PR 3 of AB#3085. Replaces the legacy launcher (whose
`WorktreeRoot = (Get-Location).Path` default was the root cause of the
main-worktree hijack bug) with a self-derivation contract over the
existing CLI verbs.

11-phase preflight chain:

1. cwd is in a git worktree (fail-fast with link to layout doc)
2. bare-repo layout preflight (refuses non-bare clones with link to
   Migrate-ToBareRepo.ps1; -SkipLayoutCheck escape hatch)
3. polyphony worktree init-apex (--dry-run on -DryRun) — self-derive
   the apex worktree path; surface every failure reason with
   remediation text
4. hijack refusal (defense-in-depth: refuse if derived path is or is
   inside the main worktree)
5. -WorktreeRoot override validation (must canonicalize to derived
   path; OrdinalIgnoreCase on Windows, normalized trailing slash)
6. intent semantics (-Intent resume + outcome=created refused)
7. .twig/ propagation (gitignored, so freshly-added apex worktrees
   lack it; copied from main on first launch; never clobbered)
8. assert-clean integration (skipped on -DryRun; surfaces dirty /
   wrong-branch / git-op-in-progress with remediation text)
9. metadata derivation (platform + repository auto-detected from
   main worktree's git remote)
10. conductor command construction (6 -m flags + apex/intent/platform/
    repo inputs; git_repo defaults to main_worktree_path)
11. conductor launch (PR #302's new-console-window pattern preserved
    for TTY-attached gate stdin)

Subsumes PR #302 per Daniel's call. Stacked on PR #311 (init-apex
extensions: --dry-run flag, runs_root/main_worktree_path output
fields). Will rebase clean once #311+#312 merge.

C# extensions to polyphony worktree init-apex:
- New --dry-run flag (read-only mirror of create/attach matrix; emits
  same hard-refusal envelopes; emits outcome=dry_run on success)
- New output fields on WorktreeInitApexResult: runs_root,
  main_worktree_path, dry_run (surface on success AND on
  after-resolution failures so launcher can format errors uniformly)
- New outcome enum value: dry_run

Tests:
- 32 new Pester tests / 32 pass — real bare-repo + git-worktree
  fixtures per test (mirrors Migrate-ToBareRepo.Tests pattern); no
  init-apex/assert-clean mocking
- 8 new xUnit tests / 29 InitApex tests pass total

Rubber-duck-validated design (see PR body for the 11 design
decisions).

Closes AB#3098.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PolyphonyRequiem
PolyphonyRequiem merged commit 5f64b23 into main May 12, 2026
1 check passed
@PolyphonyRequiem
PolyphonyRequiem deleted the feature/worktree-create branch May 12, 2026 01:16
PolyphonyRequiem added a commit that referenced this pull request May 12, 2026
…314)

* feat(worktree): add `worktree create` verb (AB#3096, PR 1b3)

Adds `polyphony worktree create --apex N --branch B [--ref R]` — the per-item worktree creation primitive used by all workflow steps that need an impl/, plan/, mg/, or evidence/ worktree under an apex run.

Mirrors the structure of `worktree init-apex` (PR #311 / PR 1b2): branch-grammar validation, bootstrap-dependency check, race-tolerant idempotent recovery.

Rubber-duck-validated decisions:

- Bootstrap dependency is structural: `{apex_root}/feature-{N}` must be a registered worktree on `feature/{N}`, not just Directory.Exists. Catches partial-init in one signal.

- Reject `feature/{N}` branches with `unsupported_branch_kind`: init-apex is the canonical path; otherwise create becomes an unprotected back-door.

- `remote_branch_exists` always wins (even with `--ref`): workflow-rerun safety; operator must explicitly fetch+branch first to avoid silent fork.

- No cwd-refusal: path is self-derived from runs_root; cwd has no impact on safety.

Stacked on PR #311. Branch off `feature/worktree-init-apex` tip; rebase onto main once #311 merges.

Part of AB#3085 (bare-repo + per-run worktree epic).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(launcher): bare-repo + per-run-worktree rework (AB#3098, PR 3)

PR 3 of AB#3085. Replaces the legacy launcher (whose
`WorktreeRoot = (Get-Location).Path` default was the root cause of the
main-worktree hijack bug) with a self-derivation contract over the
existing CLI verbs.

11-phase preflight chain:

1. cwd is in a git worktree (fail-fast with link to layout doc)
2. bare-repo layout preflight (refuses non-bare clones with link to
   Migrate-ToBareRepo.ps1; -SkipLayoutCheck escape hatch)
3. polyphony worktree init-apex (--dry-run on -DryRun) — self-derive
   the apex worktree path; surface every failure reason with
   remediation text
4. hijack refusal (defense-in-depth: refuse if derived path is or is
   inside the main worktree)
5. -WorktreeRoot override validation (must canonicalize to derived
   path; OrdinalIgnoreCase on Windows, normalized trailing slash)
6. intent semantics (-Intent resume + outcome=created refused)
7. .twig/ propagation (gitignored, so freshly-added apex worktrees
   lack it; copied from main on first launch; never clobbered)
8. assert-clean integration (skipped on -DryRun; surfaces dirty /
   wrong-branch / git-op-in-progress with remediation text)
9. metadata derivation (platform + repository auto-detected from
   main worktree's git remote)
10. conductor command construction (6 -m flags + apex/intent/platform/
    repo inputs; git_repo defaults to main_worktree_path)
11. conductor launch (PR #302's new-console-window pattern preserved
    for TTY-attached gate stdin)

Subsumes PR #302 per Daniel's call. Stacked on PR #311 (init-apex
extensions: --dry-run flag, runs_root/main_worktree_path output
fields). Will rebase clean once #311+#312 merge.

C# extensions to polyphony worktree init-apex:
- New --dry-run flag (read-only mirror of create/attach matrix; emits
  same hard-refusal envelopes; emits outcome=dry_run on success)
- New output fields on WorktreeInitApexResult: runs_root,
  main_worktree_path, dry_run (surface on success AND on
  after-resolution failures so launcher can format errors uniformly)
- New outcome enum value: dry_run

Tests:
- 32 new Pester tests / 32 pass — real bare-repo + git-worktree
  fixtures per test (mirrors Migrate-ToBareRepo.Tests pattern); no
  init-apex/assert-clean mocking
- 8 new xUnit tests / 29 InitApex tests pass total

Rubber-duck-validated design (see PR body for the 11 design
decisions).

Closes AB#3098.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Daniel Green <dangreen@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant