Skip to content

Bootstrap-BareRepo.ps1 for fresh-clone onboarding (#420) - #441

Merged
PolyphonyRequiem merged 1 commit into
mainfrom
feat/bootstrap-bare-repo
May 17, 2026
Merged

Bootstrap-BareRepo.ps1 for fresh-clone onboarding (#420)#441
PolyphonyRequiem merged 1 commit into
mainfrom
feat/bootstrap-bare-repo

Conversation

@PolyphonyRequiem

Copy link
Copy Markdown
Owner

Summary

Closes #420.

Adds scripts/Bootstrap-BareRepo.ps1, the sister script to Migrate-ToBareRepo.ps1. Migrate converts an existing operator clone; Bootstrap starts from nothing and produces the canonical bare-repo + per-run-worktree layout in a single step.

This closes a long-standing onboarding gap: the polyphony-bootstrap skill checklist and docs/onboarding-guide.md § 2 both assumed the operator already had a normal clone they could migrate. Fresh onboardings ("clone a repo for the first time and set it up bare") had to follow the manual recipe in docs/per-run-worktree-layout.md — and that recipe trips on safe.bareRepository=explicit on Windows unless the operator already knows the --git-dir workaround.

Signature

Bootstrap-BareRepo.ps1 -RemoteUrl <url>
  [-ParentDir <path>]   # default: ~/projects
  [-RepoName <name>]    # default: derived from URL leaf
  [-MainBranch <name>]  # default: remote HEAD (queried post-clone)
  [-Commit]             # default: dry-run

Design highlights

State classification up front. Every invocation computes one of full_bootstrap | recover_missing_main | recover_missing_runs | already_bootstrapped | refused before doing anything destructive. Dry-run prints the classification + the plan. Tests assert each classification's output.

Idempotency. Re-running on a canonical layout is a no-op success. Narrow partial-state recovery is supported:

  • bare present + main missing → git fetch --prune origin then add the missing worktree on the bare's HEAD branch
  • bare + main present + runs missing → mkdir the runs root

Both recovery paths are gated on the existing bare's origin URL matching -RemoteUrl (conservative normalization: strip trailing / and trailing .git, case-insensitive; does not equate https with ssh forms).

Identity verification. Main-worktree validity is double-checked: git -C $main rev-parse --path-format=absolute --git-common-dir must point at the bare, AND the bare's worktree list --porcelain must include the main path. Both directions must agree.

Refusals (exit 3, no -Force override).

  • bare path exists but isn't a bare repo
  • existing bare has no origin or a mismatched origin
  • main path exists but isn't backed by our bare
  • target branch is already checked out in another worktree
  • remote is empty / unborn HEAD (refuse before worktree add so the error is clear)
  • runs path exists but isn't a directory

There is no -Force switch — unlike Migrate-ToBareRepo there's no legacy-clone-preserved fallback that would justify bypassing identity checks.

Windows safety. Every probe against the bare uses git --git-dir=<bare> rather than git -C <bare>. Works under git config --global safe.bareRepository explicit (the global default on my box, and the upstream-recommended hardening).

Tests

31 Pester tests in scripts/Bootstrap-BareRepo.Tests.ps1, real git init in temp directories — no mocks for git itself. Mirrors the pattern in Migrate-ToBareRepo.Tests.ps1. Covers:

  • URL parsing: https / https with .git / https with trailing / / git@host: ssh shortcut / ssh:// / ADO /_git/ / Windows file paths / empty
  • Test-RemoteUrlMatch equivalence (positive + negative cases, including the deliberate https-vs-ssh non-equivalence)
  • Happy path: full bootstrap creates bare + main + runs
  • Parent dir is created if missing
  • RepoName derivation when -RepoName is omitted
  • Non-main default branch detection (master)
  • Explicit -MainBranch override beats auto-detect (develop)
  • Idempotency: re-run reports "already bootstrapped"
  • Recovery: missing main worktree
  • Recovery: missing runs root
  • Refusal: pre-existing non-bare at bare path
  • Refusal: existing bare with mismatched origin
  • Refusal: main path exists but isn't a worktree
  • Refusal: target branch already checked out elsewhere
  • Refusal: empty/unborn remote
  • Dry-run: prints plan, creates nothing, classifies correctly
  • Preflight: RepoName with path separators, ParentDir pointing at a file

All 31 pass locally (~40s).

Other wiring

  • install.ps1 — added to both the download loop and the $expectedLaunchers verification list so it lands in ~/.polyphony/bin/ alongside the rest.
  • .github/skills/polyphony-bootstrap/SKILL.md § 2 — one-line addition pointing at Bootstrap-BareRepo.ps1 for the fresh-clone case (and at Migrate-ToBareRepo.ps1 for the existing-clone case).
  • docs/onboarding-guide.md § 2 — new "Fresh clone (no existing local checkout)" subsection with the canonical invocation and a note about safe.bareRepository=explicit.

Out of scope

Verification

  • 31/31 Pester tests pass locally.
  • No conductor / jinja / version-drift lints touched (the change is launcher + docs only).
  • install.ps1 is structurally unchanged — same loop, one extra string in two lists.

Sister script to Migrate-ToBareRepo.ps1: that one converts an existing
operator clone; this one starts from nothing and produces the canonical
bare-repo + per-run-worktree layout in a single step.

Signature:
  Bootstrap-BareRepo.ps1 -RemoteUrl <url>
    [-ParentDir <path>]   # default: ~/projects
    [-RepoName <name>]    # default: derived from URL leaf
    [-MainBranch <name>]  # default: remote HEAD (queried post-clone)
    [-Commit]             # default: dry-run

Behavior:
  - Classifies state up front: full_bootstrap | recover_missing_main |
    recover_missing_runs | already_bootstrapped | refused. Dry-run mode
    prints the classification + the plan it would execute.
  - Idempotent: re-running on a canonical layout is a no-op success.
  - Narrow partial-state recovery: bare present + main missing -> add the
    missing worktree; bare + main present + runs missing -> just mkdir.
    Both gated on the bare's `origin` URL matching `-RemoteUrl` after
    conservative normalization (trailing `/` + trailing `.git` stripped,
    case-insensitive). https <-> ssh forms are NOT considered equivalent.
  - Refuses (exit 3) on identity mismatches and pre-existing collisions:
    bare path that isn't bare, main path with a different `--git-common-dir`,
    target branch already checked out in another worktree, empty/unborn
    remote, non-directory at the runs path.
  - Always probes via `git --git-dir=<bare>`, never `git -C <bare>` —
    works under `safe.bareRepository=explicit`.
  - No `-Force` switch: identity mismatches and path conflicts are always
    fatal because there is no equivalent of the legacy-clone-preserved
    side door that Migrate-ToBareRepo has.

Tests: 31 Pester tests, real `git init` in temp dirs, no mocks. Covers
URL parsing for https/ssh/ssh-shortcut/ADO/local-file forms; default-
branch detection (main/master/explicit override); idempotency; both
recovery paths; six refusal cases; dry-run; and preflight failures.

Wiring:
  - install.ps1 fetches it into ~/.polyphony/bin/ alongside the other
    launcher scripts (download loop + $expectedLaunchers list).
  - polyphony-bootstrap skill § 2 and docs/onboarding-guide.md § 2 both
    point at it for the fresh-clone case (Migrate stays as the
    existing-clone path).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PolyphonyRequiem
PolyphonyRequiem merged commit 34b3aca into main May 17, 2026
1 check passed
PolyphonyRequiem added a commit that referenced this pull request May 17, 2026
) (#447)

Seven small fixes across `polyphony-runtime`, `polyphony-bootstrap`,
and `docs/onboarding-guide.md` that close the epic-414 onboarding
friction tracked in #425. Consolidated because each is a one-line to
one-paragraph change and they all hit the same three documents.

1. polyphony-runtime: removal of `twig list-workspaces` reference.
   Verified the in-repo skill already uses the correct `twig
   workspace` verb — the operator's report was against a stale copy.
   No change required.

2. polyphony-runtime: disambiguate "repo root" vs "main worktree".
   The decision-tree question now asks whether `.polyphony-config/` is
   in the **main worktree** (the `./<repo>/` checkout under the
   bare-repo + worktree layout) rather than ambiguously "in the repo
   root". A new admonition at the top of the preflight section spells
   out that all polyphony commands — and the launcher — must run from
   inside the main worktree, not the bare-repo root.

3. polyphony-bootstrap: document the `safe.bareRepository=explicit`
   Windows pitfall. Added a sub-bullet to § 2 explaining that plain
   `git -C <repo>.git ...` is rejected with the
   `safe.bareRepository is 'explicit'` error on Microsoft-managed
   boxes, and showing the `--git-dir=<repo>.git` form. Notes that
   `Bootstrap-BareRepo.ps1` and `Sync-BareRepo.ps1` already use the
   correct form — the pitfall only bites raw `git` calls by hand.

4. polyphony-bootstrap: call out `twig init --force` when the repo
   ships a `.twig/config` (twig's own repo does this). New blockquote
   in § 1 right after the existing `twig init` recipe.

5. polyphony-bootstrap: fix `twig process $type` → `twig process
   --type $type` in three places (§ 3.5 command, § 4 walk-every-event
   command, § 6 checklist line). Added a blockquote at § 3.5
   explaining that twig 0.77+ requires the flag form even though
   twig's own help still shows the positional form (the upstream
   inconsistency).

6. polyphony-bootstrap: add a "how to pick a smoke-test work item"
   recipe to § 1. `twig workspace --tree` is empty on a fresh sprint,
   so the recipe now also offers `twig query --state Doing` and
   `twig query --state "To Do" --type <leaf-type>` as sprint-pin-free
   fallbacks.

7. onboarding-guide § 4 "What Gets Generated": update the directory
   tree to show the per-role agent-guidance layout
   (`architect.md`/`coder.md`/`reviewer.md`) the bootstrap actually
   emits, instead of the stale per-type layout
   (`epic.md`/`user-story.md`/`task.md`). Replaced the stale note
   below the tree with an accurate description of the per-role +
   optional per-role-per-type refinement layout, with a pointer to
   § 7 for the per-role vs all-agents authoring contract.

Verification:

- `pwsh ./tests/lint-type-agnostic.ps1` — PASS (57 files scanned across
  surface 'all'; placeholder `<Type>` / `<leaf-type>` used in twig
  recipes to avoid the lint).
- All other Pester lints green (lint-pwsh-jinja-bareword,
  lint-prose-children, lint-sync-after-mutation, lint-version-drift,
  lint-no-tracked-polyphony-state, etc.).
- One pre-existing `lint-psscriptroot-paths` finding on
  `Bootstrap-BareRepo.ps1:79,140` (from PR #441) reproduces on `main`
  and is unrelated.

Closes #425.

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.

MED: Bootstrap-BareRepo.ps1 helper for fresh-clone bare-repo + worktree layout

1 participant