Skip to content

Improved entire enable flow on folders that are not git repos yet#978

Open
Soph wants to merge 12 commits intomainfrom
soph/enable-with-git-flow
Open

Improved entire enable flow on folders that are not git repos yet#978
Soph wants to merge 12 commits intomainfrom
soph/enable-with-git-flow

Conversation

@Soph
Copy link
Copy Markdown
Collaborator

@Soph Soph commented Apr 17, 2026

Summary

Lets entire enable bootstrap a brand-new folder all the way to a working repo — git init, agent setup, initial commit, and a matching GitHub repository via the gh CLI — instead of exiting with "Not a git repository".

Flow

~ cd my-new-project
~ entire enable

No git repository in "my-new-project". Initialize one here? [Y/n]

━ Setting up git repository
✓ Initialized empty git repository

Create a matching repository on GitHub? [Y/n]
Choose the GitHub owner for the new repository:
  > my-username
    my-org
Repository name:         [my-new-project]
Repository visibility:   [Private / Public / Internal]
Initial commit:
  > Commit with default message "Initial commit"
    Customize message...
    Skip — I'll commit manually later

━ Enabling Entire
Agent: Claude Code

Installed 7 hooks for Claude Code
✓ Project configured (.entire/settings.json)
✓ Created orphan branch 'entire/checkpoints/v1' for session metadata

━ Publishing to GitHub
✓ Created initial commit (Initial commit)
✓ Created my-username/my-new-project (private)
  https://github.com/my-username/my-new-project
✓ Pushed initial commit to origin

Done.

All prompts go through NewAccessibleForm, so ACCESSIBLE=1 works
as expected.

Flow details worth calling out

  • Fresh-machine safety — if git identity isn't configured anywhere,
    we source user.name / user.email from gh api user (falling back
    to the GitHub no-reply address when the user's email is private),
    then prompt, then fail with a pointer to git config --global.
    Values are written to the local repo config only. The initial commit
    also runs with -c commit.gpgsign=false so a broken or absent signer
    doesn't block the first commit.
  • Default branch — the pre-push hook installed during agent setup
    would otherwise push entire/checkpoints/v1 alongside main on the
    first push, which GitHub then picks as the repo's default branch. The
    initial push uses git push -q --no-verify -u origin HEAD; only
    main lands on the remote and the checkpoint metadata branch is
    pushed normally on subsequent pushes.
  • Two-phase bootstrap — phase 1 (git init, identity, gather GH
    choices) runs before agent setup; phase 2 (git add -A + commit +
    gh repo create + push) runs after, so the initial commit captures
    .entire/, .claude/, agent hooks, and every other file setup
    writes.
  • Error paths
    • gh missing or unauthenticated → print a hint (brew install gh && gh auth login) and fall through to local-only bootstrap.
    • Ctrl+C before git init → legacy "Not a git repository" error.
    • Ctrl+C after git init → new "Bootstrap cancelled. A local git repository has been initialized but setup didn't complete. Run entire enable again to continue." message.
    • Setup fails after init → skip the commit / push; files remain untracked, user can fix and rerun.
    • Skipping the commit still creates the GitHub repo and prints the exact git add -A && git commit && git push commands to finish.

Flags

All optional; each suppresses the matching interactive prompt, so
automation can drive the flow end-to-end without a TTY.

Flag Effect
--init-repo Accept the git init prompt
--no-init-repo Decline the git init prompt (mutually exclusive with --init-repo)
--repo-name <name> GitHub repo name (validated against GitHub's charset)
--repo-owner <owner> GitHub user or org login
--repo-visibility <public|private|internal> internal only valid for orgs
--no-github Local git init + initial commit only; skip GitHub
--initial-commit-message <msg> Override the default commit message
--skip-initial-commit Skip the initial commit (mutually exclusive with --initial-commit-message)

Setting any of --repo-name / --repo-owner / --repo-visibility
implicitly answers "yes" to the GitHub-confirm prompt.

Notes for reviewers

  • gh is shelled out via exec.Command rather than pulled in as a Go
    dep. All calls go through a bootstrapRunner interface so unit tests
    don't hit the network.
  • The commit-message prompt is a 3-option huh.NewSelect (default /
    customize / skip) rather than a freeform input. "Skip" was the only
    freeform input where a skip shortcut made sense — repo name and
    identity have no sensible "no value" semantics — so we didn't try to
    generalize it.
  • The full matrix of branches (confirm init → GH yes/no → commit
    default/custom/skip → various flag combinations) is covered by unit
    tests with a mocked bootstrapRunner. Two integration-style tests
    (TestBootstrap_FreshMachine_RealGit,
    TestBootstrap_FreshMachine_NoIdentity_RealGit) run real
    git init/git commit against a temp dir isolated via HOME +
    GIT_CONFIG_GLOBAL to prove the identity + gpgsign fixes end-to-end.

Test plan

  • mise run check (fmt + lint + unit + integration + Vogon canary + roger-roger external-agent canary) — already green locally
  • Manual: fresh empty folder → entire enable with no flags walks through prompts and pushes to GitHub; default branch on the remote is main, no entire/checkpoints/v1 on the remote
  • Manual: fresh folder → entire enable --init-repo --no-github --initial-commit-message "init" --agent claude-code completes non-interactively, no GitHub call, one commit
  • Manual: fresh folder → entire enable --init-repo --no-github --skip-initial-commit leaves files unstaged and prints the manual-commit hint
  • Manual: Ctrl+C at owner/name/visibility/commit-message prompt → "Bootstrap cancelled" message, .git/ still in place
  • Manual: entire enable --init-repo --no-init-repo → cobra's mutually-exclusive error
  • Manual: existing git repo → entire enable behaves exactly as before (bootstrap skipped entirely)
  • Manual: machine without gh → hint printed, local git init + initial commit still succeed

Soph and others added 3 commits April 17, 2026 22:18
When `entire enable` runs in a folder that isn't a git repository, prompt
to `git init` one, then optionally create a matching GitHub repository via
the gh CLI: pick owner (user or org), suggest a repo name from the folder,
check availability, pick visibility, confirm initial commit message, and
push. Falls back to local-only if gh is missing or unauthenticated.

Flags (all optional; each suppresses its matching prompt):
- `--init-repo` / `--no-init-repo`: accept or decline the git init prompt
- `--repo-name`, `--repo-owner`, `--repo-visibility`
- `--no-github`: local git init only
- `--initial-commit-message`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: b4cbdcf4b56c
The initial commit created by `entire enable` bootstrap could fail in two
common fresh-machine scenarios:

- No global `user.name`/`user.email` configured yet — git commit would
  refuse with "please tell me who you are".
- `commit.gpgsign=true` inherited from a global config with no working
  signer — git commit would fail trying to sign.

Fix both:

- New `ensureGitIdentity` runs before the initial commit: reuses identity
  already set at any scope; otherwise sources name + email from
  `gh api user` (falling back to `{id}+{login}@users.noreply.github.com`
  when the email is private); otherwise prompts interactively or errors
  non-interactively with a pointer to `git config --global`. Values are
  written to the local repo config only; global state is never touched.
- The bootstrap commit always runs with `-c commit.gpgsign=false` so a
  broken or unavailable signer does not break the first commit. The
  user's global signing preference for subsequent commits is unchanged.

Includes real-git regression tests that isolate HOME/GIT_CONFIG_* to
prove the fix on a truly fresh environment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f6d5ccfe5742
Before: `entire enable --init-repo --no-init-repo` silently honored
`--no-init-repo` and fell through to the legacy "Not a git repository"
error. Automation could mistakenly pass both and never see a flag error.

Now: cobra's MarkFlagsMutuallyExclusive surfaces a clear error at parse
time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 096f98a24b33
@Soph Soph requested a review from a team as a code owner April 17, 2026 21:11
Copilot AI review requested due to automatic review settings April 17, 2026 21:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends entire enable to support bootstrapping a brand-new folder into a usable git repository (including an initial commit) and optionally creating/pushing to a matching GitHub repository via the gh CLI, instead of immediately failing when no git repo is present.

Changes:

  • Add a GitHub bootstrap flow (git init, optional gh repo create, initial commit with identity + commit.gpgsign=false handling).
  • Add entire enable flags to drive the bootstrap flow non-interactively (--init-repo, --no-github, --repo-*, etc.).
  • Add unit + regression-style tests covering slugification/validation, gh helpers, bootstrap paths, and fresh-machine git identity behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
cmd/entire/cli/setup_github.go Implements the new bootstrap flow (git init + optional gh repo creation + initial commit + identity handling).
cmd/entire/cli/setup_github_test.go Adds tests for repo name handling, gh helpers, bootstrap behavior, and real-git regressions.
cmd/entire/cli/setup.go Hooks the bootstrap flow into entire enable and adds new bootstrap-related flags.

Comment thread cmd/entire/cli/setup_github.go Outdated
Comment thread cmd/entire/cli/setup_github.go Outdated
Comment thread cmd/entire/cli/setup_github.go Outdated
Comment thread cmd/entire/cli/setup_github_test.go Outdated
Comment thread cmd/entire/cli/setup_github_test.go Outdated
Comment thread cmd/entire/cli/setup_github.go Outdated
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 7023b4e. Configure here.

Comment thread cmd/entire/cli/setup_github.go Outdated
Comment thread cmd/entire/cli/setup_github.go
Soph and others added 9 commits April 18, 2026 10:51
The bootstrap ran `git add -A` + `git commit` before agent setup, so the
initial commit only captured files the user already had in the folder.
Everything `entire enable` writes afterwards — `.entire/settings.json`,
`.entire/.gitignore`, `.claude/agents/*`, `.claude/settings.json`, agent
hooks — landed as untracked files, which is a bad first impression and
forces the user to stage and commit them separately.

Split the bootstrap into two phases around agent setup:

- `runGitHubBootstrapInit` runs before: `git init`, ensure git identity,
  gather owner/name/visibility via gh, resolve commit message. All
  prompts happen here so the main setup output isn't interleaved with
  input.
- `runGitHubBootstrapFinalize` runs after: `git add -A` + `git commit` +
  `gh repo create --source=. --push`. By this point the `.entire/`,
  `.claude/`, and hook files written by setup are on disk, so they land
  in the initial commit.

The finalize step runs via a deferred closure on a named return so every
early-return path in `enable` (non-interactive `--agent`, already
configured, etc.) still triggers it on success. On setup failure the
finalize is skipped — the user can fix the issue and rerun; any partial
state is just untracked files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 14f3e6df5d51
`gh repo create --push` invoked the newly installed `pre-push` hook,
which pushed `entire/checkpoints/v1` alongside the default branch.
GitHub then picked `entire/checkpoints/v1` as the repository's default
branch because it was one of the refs in the initial push.

Split the step into `gh repo create` (no push) followed by an explicit
`git push --no-verify -u origin HEAD`. `--no-verify` bypasses the
pre-push hook for this one push; the checkpoint branch is skipped,
which is safe because a fresh repo has no sessions to checkpoint yet.
Subsequent pushes still go through the hook normally.

Verified against a real GitHub repo: only `main` lands on the remote,
and GitHub's default branch is `main`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e207ae75dc18
Before diving into owner/name/visibility prompts, ask a simple
"Create a matching repository on GitHub?" yes/no so users have an
obvious path to local-only without needing to know the --no-github flag.

The prompt is skipped when the user has already expressed intent via
any of --repo-name / --repo-owner / --repo-visibility, and in
non-interactive contexts the default stays "yes" to preserve the
documented happy path for automation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 6f71edf427dc
Before this change, Ctrl+C at any bootstrap prompt after `git init` had
already run surfaced the misleading "Not a git repository. Please run
'entire enable' from within a git repository." error. The bootstrap
shared a single `errBootstrapDeclined` sentinel for both "user said no
before init" and "user aborted a prompt after init".

Split into two sentinels:

- `errBootstrapDeclined` stays as "user declined before init" —
  setup.go keeps the legacy message.
- `errBootstrapInterrupted` is new: used by every prompt that happens
  after `git init` (owner, name, visibility, commit message, identity,
  GitHub-confirm). setup.go now prints "Bootstrap cancelled. A local
  git repository has been initialized but setup didn't complete. Run
  `entire enable` again to continue." and returns a silent error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 334deadad1ea
Replace the freeform commit-message input with a 3-option select:

  > Commit with default message "Initial commit"
    Customize message...
    Skip — I'll commit manually later

If the user picks "Skip", we still create the GitHub repo (if
requested), but we don't run `git add`/`git commit`/`git push` and we
print the exact commands to finish up manually. This handles the case
where a user wants to review what setup wrote (e.g. adjust the
generated `.entire/` files, add a `.gitignore`) before their first
commit — previously the only escape was Ctrl+C, which aborted the
whole flow.

Also adds a `--skip-initial-commit` flag for non-interactive parity
with the other prompts. `--skip-initial-commit` and
`--initial-commit-message` are mutually exclusive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: c2187640bce8
Address four UX issues with the bootstrap's console output:

1. Fix ordering — "Ready." and the "Note: Session checkpoints require
   at least one commit" hint from the enable flow printed before the
   bootstrap's commit + push, which was both misleading ("not ready
   yet") and inaccurate ("about to commit right now"). A new
   SuppressDoneMessage option lets the bootstrap flow tell the enable
   flow to skip those; the bootstrap finalize prints its own summary
   ending in "Done."

2. Quiet the initial push — `git push -q` silences the verbose
   "Enumerating objects..." / "Compressing objects..." / "Writing
   objects..." progress. Errors still come through.

3. Capture `gh repo create` output instead of streaming it. The tool's
   own "✓ Created repository..." / "✓ Added remote..." lines
   duplicated what we print ourselves; capturing keeps the output
   single-voice. Stderr is surfaced on failure via ghRunnerErr.

4. Section headers — light "━ Setting up git repository" / "━ Enabling
   Entire" / "━ Publishing to GitHub" / "━ Finalizing" separators
   between the three phases, giving visual structure to what was a
   wall of mixed output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: f6504427d0bc
Addresses code-review feedback on #978:

- Drop `RunInteractive` from `bootstrapRunner`, `execRunner`, and
  `fakeRunner`. Since `gh repo create` and `git push` both moved to
  captured-stdout calls (`RunInDir`) in the output-polish commit,
  nothing calls `RunInteractive` anymore. Removing it also eliminates
  the `execRunner.RunInteractive` issue where streams were pinned to
  `os.Stdin/Stdout/Stderr` rather than cobra's configured writers, and
  the `fakeRunner.RunInteractive` issue where unregistered calls
  returned nil silently.
- Drop the unused `w io.Writer` parameter from `resolveVisibility` and
  `resolveCommitMessage`. The `_ = w` placeholder made it look
  intentional; it wasn't.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 53160af9e311
Addresses #978 review: ensureGitIdentity previously wrote both
`user.name` and `user.email` to local config whenever either one was
missing. A user with `user.name` set globally but no `user.email`
would have their configured name silently replaced by the
gh-derived value.

Now we:
- Read existing `user.name` and `user.email` independently.
- Pass both through to resolveGitIdentity along with any gh-sourced
  fallbacks; per-field, use the existing value if set, else the gh
  value, else prompt (interactive) or error (non-interactive).
- Write only the fields that weren't already configured. A partial
  global config is preserved exactly as-is.

Also prompts only for the fields that are still missing after the
gh/existing fallbacks, so a user who's missing just the email doesn't
have to re-type their name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: e2c9b7aae2ed
Addresses remaining #978 review feedback:

- **Regex**: GitHub allows repo names starting with `.`, `-`, or `_`
  (`.github` being the canonical example). The previous regex
  `^[A-Za-z0-9][A-Za-z0-9._-]*$` rejected valid names when passed via
  `--repo-name` or entered interactively. Loosened to allow any of the
  permitted characters as the leading char; `validateRepoName`
  explicitly rejects `.` and `..` as those are path-reserved.
- **Non-interactive dup output**: `confirmInitRepo` no longer prints
  "Not a git repository. Pass --init-repo..." to stdout when
  non-interactive. The caller (enable RunE) owns the single "Not a git
  repository" line on stderr, now extended with the `--init-repo`
  hint.
- **Test flakiness**: `TestBootstrap_FreshMachine_NoIdentity_RealGit`
  previously tried to make `gh` unavailable by stomping on PATH, then
  restoring common dirs like `/opt/homebrew/bin` that commonly contain
  `gh`. Replaced with a `ghFailingRunner` wrapper that forces `gh`
  calls to fail while letting real `git` calls through, plus
  `GH_TOKEN`/`GITHUB_TOKEN` unsets as defense-in-depth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 5b72a2385094
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants