When to use
A project that does not exist yet is about to. Trigger phrases: new project · start a new project · create a new repo · spin up a repo · scaffold <name> · bootstrap <name> · set up a project for X · make this a real project · init a repo · let's build X (when no repo exists).
Not this skill:
| Situation | Go instead |
|---|---|
| Repo already exists | Just do the work; polish/git-wrapup for shipping |
| An MCP server — or anything else whose framework owns the scaffold | Stop. Run the framework's scaffolder (bunx @cyanheads/mcp-ts-core init <name> for MCP servers), then follow its setup skill — this skill's conventions are already baked in there |
| One-off script, throwaway analysis | Scratchpad file. A repo is not the answer to every task |
Ask once, then build
Everything below has a default, so a scaffold rarely needs to stall. Ask one round only for what is genuinely unknown and changes the shape:
| Decision | Default |
|---|---|
| Name | Hyphenated, lowercase, says what it is. Names an agent will re-read benefit from being explicit over short |
| Location | ~/Developer/github/<name>/ — every repo is a sibling there, no nested roots, no per-category dirs |
| Visibility | Private unless it is meant to be published or shown off. Client work is always private |
| License | Apache-2.0 for public, none needed for private |
| Runtime | Bun + TypeScript. Python → uv |
| Version | 0.1.0 |
| Publishes? | No, unless said otherwise — that decides npm metadata, badges, and whether the changelog ships in the package |
1 — Directory and git
mkdir -p ~/Developer/github/<name> && cd ~/Developer/github/<name>
git init -b main
-b main at init — never rename a branch after the fact.
2 — Skeleton
Modular from the first commit. Directories are domains, not type-buckets: src/<domain>/ beats src/utils/, src/helpers/, src/types/. One primary export per non-trivial file, named for what it exports (token-store.ts, not store.ts). A module's public surface is its index.ts barrel; cross-module imports go through the barrel, never into another module's internals. Tests mirror src/. scripts/ holds repo tooling, docs/ holds design docs and decisions.
Stack defaults — 2026, no legacy patterns:
| Language | Defaults |
|---|---|
| TypeScript | Bun runtime + package manager · ESM only ("type": "module") · strict + noUncheckedIndexedAccess + verbatimModuleSyntax + erasableSyntaxOnly + moduleResolution: "bundler" · Biome for lint and format (not ESLint/Prettier) · Vitest · Zod at system edges · engines pinned (bun >=1.3, node >=24) · lockfile committed |
| Python | uv for everything (uv venv in-project, uv add, uv run) · pyproject.toml · ruff · pytest |
Files at the root, all of them present before the first commit:
package.json— complete metadata, not just name/version:description(see below),keywords,repository,bugs,homepage,author,license,engines,filesif publishedtsconfig.json,biome.json,bunfig.toml.gitignore—.envin it before.envexists.env.example— every var the code reads, with a comment and a safe default. Real secrets never leave.envLICENSE(public repos)README.md→references/readme.mdCLAUDE.md+AGENTS.md→references/agent-protocol.mdCHANGELOG.md+changelog/+scripts/build-changelog.ts→references/changelog.md
Wire the gate before the first commit. One command that a later session can trust: bun run check (typecheck + lint + test), or devcheck if the project grows its own. A project whose gate arrives later never gets one.
Supply-chain hold — bunfig.toml gets [install] minimumReleaseAge = 259200 (3 days). New projects inherit the guard rather than bolting it on after an incident.
3 — The three documents
Read the reference file before writing each — they carry the structure, not this file:
| Document | Reference |
|---|---|
README.md |
references/readme.md |
CLAUDE.md + AGENTS.md symlink |
references/agent-protocol.md |
changelog/ system |
references/changelog.md |
One description string, three places — package.json description, the README tagline, and the GitHub repo description are the same sentence, verbatim. They drift the moment they are written independently.
4 — GitHub repo
gh repo create <name> --private --source=. --remote=origin \
--description "<the one description string>"
gh repo edit --add-topic "<topic>,<topic>,..."
gh repo edit --homepage "<url>" # only if there is a real one
Description: one line, ~60–120 chars, states what it does and how it's reached. No "A tool for…", no adjectives.
Topics: 8–20, lowercase-hyphenated, covering domain terms, the tech (typescript, bun), the ecosystem it plugs into, and the owner handle as a personal tag. Topics are how a stranger finds the repo — pick the words they would type, not the words the code uses.
--private unless the decision above said public. Flipping private→public later is one command; the reverse leaks.
5 — Initial commit and push
A new-project request authorizes the scaffold commit, the repo creation, and the initial push — that is what "set up the repo" means. It does not authorize any commit after that one; from there the standing rule applies (commit only when asked).
git add .
git commit -m "chore: scaffold <name>" -m "<one line: what this project is>"
git push -u origin main
Then verify the surface actually came up — the repo page, the description, the topics, the README rendering.
6 — Register it
A new repo that nothing points at is invisible next session. Same turn: add it wherever the environment tracks projects — the workspace CLAUDE.md repo list, an inventory or catalog file, a dashboard's source data. If the project earns a standing commit grant or a gate command a future session must know, that goes in the same edit.
Checklist
-
~/Developer/github/<name>/,git init -b main - Skeleton is modular by domain; no
utils/catch-all -
package.jsonmetadata complete;.gitignorecovers.env;.env.examplewritten - Gate command exists and passes on the empty scaffold
- README follows
references/readme.md -
CLAUDE.mdwritten for a cold agent;AGENTS.mdsymlink in place -
changelog/template.md,changelog/0.1.x/0.1.0.md, generatedCHANGELOG.md, builder script +changelog:build/changelog:checkscripts - Description string identical in
package.json, README tagline, repo description - Repo created with description + topics; initial commit pushed; surface verified
- Project registered wherever the environment tracks repos