gh-weld-repo
Create a GitHub repo from a local directory — infer what you can, ask for the rest, confirm before acting.
NEVER
NEVER chain bash commands with
&∨Instead: One command per Bash tool call. Why: Claude Code's safety system fires on ambiguous multi-command calls and interrupts the session mid-flow.NEVER use
|(pipe) in Bash tool calls Instead: Redirect output to a file under.weld/tmp/with>and read it back with the Read tool. Why: Claude Code stops execution when it encounters a pipe — no error, no warning, the agent just halts mid-flow. Note:|in markdown table cell syntax is unaffected.NEVER use
$(...)command substitution inside bash commands Instead: Run the command as a standalone Bash call and reference the output in subsequent steps. Why: Claude Code's permission system prompts on$()during execution.NEVER call
gh repo createbefore the confirm gate in Phase 3 Instead: Always show the full settings summary and receivecbefore acting. Why: Repo creation is hard to undo — wrong name or visibility requires deletion and recreation.NEVER skip checking for an existing remote before adding one Instead: Run
git remote -vand read the output beforegit remote add origin. Why: Adding a duplicate remote fails with an error and leaves the repo in a broken state.NEVER pass a multiline body containing
#-prefixed lines as an inlineghargument Instead: Write to a temp file (e.g..weld/tmp/repo-desc.md) with the Write tool and pass via--body-file. Why: Headers in inline strings trigger an un-suppressible Claude Code permission prompt.NEVER use
find,grep, orcatin Bash tool calls Instead: Use Glob to find files, Grep to search content, and Read to read files. Why: Built-in tools have tighter permissions and don't trigger Claude Code safety prompts the way raw shell commands can.NEVER use
mktempor$(mktemp ...)Instead: Use a fixed path under.weld/tmp/with the Write tool. Why:mktempuses platform-dependent/tmp/paths, and the$()substitution it requires triggers Claude Code permission prompts.NEVER offer Enter (empty line) as the way to accept a default in a prompt Instead: Bind every default to an explicit keypress (e.g.
(n) none, replykto keep) — never[default]/"press Enter". Why: Claude Code's CLI can't submit an empty line, so an Enter-default is unreachable and the user is stuck.NEVER commit the scaffold straight to
mainon a fresh bootstrap Instead: Keep gh's initial commit (the--gitignore/--licensefiles) as the base onmain, then create a branch and commit the scaffold there. Why: A scaffold landed directly onmainbypasses branch → PR → merge review and leavesgh-weld-adoptnothing to formalize, dead-ending the close-the-loop chain (issue #84).
Phase 1 — Inspect
Infer the repo name from the current directory's folder name (pwd). Use Glob to detect the stack:
| Indicator file | Stack | .gitignore template |
|---|---|---|
package.json |
Node.js | Node |
go.mod |
Go | Go |
Cargo.toml |
Rust | Rust |
pyproject.toml or requirements.txt |
Python | Python |
pom.xml or build.gradle |
Java | Java |
Gemfile |
Ruby | Ruby |
If a LICENSE file exists, read the first line to infer the license identifier (e.g. MIT, Apache-2.0).
Use Glob to check for README.md. If found, Read it and extract the first complete sentence from the first paragraph after any leading # heading (a sentence ends at ., !, or ?) — this is the inferred description. If no sentence terminator is found, use the first line. If the result exceeds 120 characters, truncate at the nearest word boundary before the limit. If no README.md exists, or the file contains only a heading with no body paragraph, or the extracted text is empty after stripping whitespace, the inferred description is blank. Store this value for Phase 2.
If git remote -v shows origin is already configured: warn the user and ask (c)ontinue anyway / (a)bort. On abort, stop.
Note whether git status reports "not a git repository" — needed in Phase 4.
Phase 2 — Interview
Before asking questions, think about what makes these settings good, not just valid: a repo name should be specific and searchable (someone scanning a repo list should guess what it does); a description should say what the project is and who it's for in one line, not restate the name; topics should be discovery terms others would actually search. Let that shape what you suggest and what you flag for reconsideration.
Check whether the inferred repo name looks right at github.com/<username>/<name>. Generic names (test, project, new, app, demo) are red flags — flag the name as a suggestion to reconsider and move it to question #5.
Ask only for settings still marked (ask). Show inferred values first so the user can accept or override. One question at a time.
Order:
- Visibility: "Public or private? Reply (p)ublic or pri(v)ate." Map
p→public,v→private. Public is the recommended default — replypto accept it. Do not rely on Enter; require a keypress. - Description: If an inferred description exists (from Phase 1), show
"Description: [<inferred>] — accept or enter a new one?". If blank, ask"One-line repo description?". - License (only if no LICENSE file found): present a single-keypress menu:
Map the keypress to the license identifier:License? Single keypress: (m) MIT (a) Apache-2.0 (g) GPL-3.0 (b) BSD-3-Clause (i) ISC (p) MPL-2.0 (u) Unlicense (n) nonem→MIT,a→Apache-2.0,g→GPL-3.0,b→BSD-3-Clause,i→ISC,p→MPL-2.0,u→Unlicense,n/Enter→none. The selected identifier is passed togh repo create --license "<id>"in Phase 4 (omit--licenseentirely whennone). - Topics: "Suggested topics:
<detected stack>. Accept, or enter your own?" - Name (only if user wants to override): "Repo name? Reply
kto keep<inferred>, or type a new name." Do not rely on Enter to accept the inferred name.
Phase 3 — Confirm
Display the full settings summary:
Name: <name>
Visibility: <public|private>
Description: <description>
Topics: <topics>
.gitignore: <template>
License: <license>
Ask: (c)onfirm / (e)dit / (a)bort
e→ ask "Which setting to change?" and loop back to Phase 2 for that field onlya→ output "Aborted. No changes made." and stopc→ proceed to Phase 4
Phase 4 — Create & Push
On a fresh repo, the scaffold goes on a branch, not straight to
main— the first body of work must flow through branch → PR → merge like every other change. Committing it tomainbypasses review and dead-endsgh-weld-adopt(see the NEVER rule, ref #84).
Create the repo
Run as separate Bash calls (no chaining):
gh repo create <name> --<public|private> --description "<description>" --gitignore "<template>"
If a license was specified (not none):
gh repo create <name> --<public|private> --description "<description>" --gitignore "<template>" --license "<license>"
If the command fails:
- Output contains "Name already taken" → return to Phase 2, ask for a different name, and retry
- Output contains "authentication" or "401" → tell the user to run
gh auth loginand retry - Any other error → surface the raw error output and stop
If topics were collected, set them after creation. The gh repo create output names the new repo as <owner>/<name> (e.g. "✓ Created repository alice/my-app on GitHub") and the repo URL is https://github.com/<owner>/<name> — take <owner>/<name> from there; do not guess the owner.
gh repo edit <owner>/<name> --add-topic "<topic>"
One gh repo edit call per topic. If a call fails, log the error and continue — topic tagging is non-critical and should not block the push.
Initialize if needed — graft onto the remote base
When Phase 1 noted "not a git repository", the repo gh just created already carries a minimal base commit on main — the --gitignore/--license files form GitHub's initial commit. Use that as the base and stack the scaffold on a branch on top of it. Do not create a second local initial commit: pushing it would be rejected as unrelated history. Run as separate Bash calls:
git init
git remote add origin <url>
git fetch origin
git reset --mixed origin/main
git branch -M main
git checkout -b setup/scaffold
git add -A
git commit -m "Scaffold project files"
git reset --mixed origin/main adopts gh's base commit as local main while leaving the scaffold files untouched in the working tree; the scaffold then lands as one commit on setup/scaffold. If the repo already has commits, skip this block — see "Existing history" below.
Add remote and push
Read the repo URL from the gh repo create output (used as <url> above).
Fresh bootstrap (the Initialize block above ran): the remote is already added and local main already matches gh's base commit — push only the scaffold branch:
git push -u origin setup/scaffold
main stays the default branch and PR base; setup/scaffold carries the reviewable scaffold.
Existing history (Initialize block skipped): if no origin remote exists, run git remote add origin <url>. Because gh initialized the remote with a base commit, reconcile before pushing so the histories aren't unrelated:
git fetch origin
git pull --rebase origin main
git push -u origin <current-branch>
Determine <current-branch> with git branch --show-current (handles main, master, or any default without guessing). If the rebase reports conflicts, resolve them before pushing.
Phase 5 — Done
Fresh bootstrap:
Repo created: <url>
Remote: origin → <url>
Base: main — gh's initial commit (.gitignore/license), the PR base
Scaffold: setup/scaffold — your project files (provisional; /gh-weld-adopt renames it)
Next: run /gh-weld-adopt → /gh-weld-ship to open a PR, squash-merge, and close.
Existing history:
Repo created: <url>
Remote: origin → <url>
Branch: <branch> is now tracking origin/<branch>