Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.7 KiB
name, description
| name | description |
|---|---|
| init-project | Scaffold a new personal project at ~/Code/<name>, push it to the user's Gitea server (didericis), and leave behind a low-dependency, text-driven repo (README, CLAUDE.md, docs/JOURNAL.md, docs/research, .claude/skills/init-entry). Interrogates the invoker for name, description, language, visibility, goals, non-goals, audience, and an optional Gitea workflow stub. Use when the user invokes /init-project or asks to "start a new project", "scaffold a repo", "spin up a new project", or similar. |
Scaffold a new personal project
A guided flow for creating a fresh project locally and on the user's self-hosted Gitea server. The output is a minimal, text-driven repo whose purpose, audience, goals, and decisions live in the repo itself — not in a tracker, not in chat, not in someone's head.
The invoker is an expert developer who values clarity and low dependencies. Don't add tooling, frameworks, or boilerplate beyond what's described here.
Preflight
Before asking the invoker anything:
- Verify both
GITEA_URLandGITEA_TOKENare set in the environment. If either is missing, stop and tell the invoker exactly which one to set. Suggest:GITEA_URL— base URL of the Gitea server (e.g.https://gitea.example.tld), no trailing slash.GITEA_TOKEN— a personal access token withwrite:repositoryscope.
- Verify
~/Codeexists; create it if not. - The Gitea repo owner is always
didericis. Do not ask.
Step 1 — Interrogate
Gather inputs in two batches. Keep it tight.
Batch A — free text (ask in one chat message, parse the reply):
- Name — a slug. Must match
^[a-z][a-z0-9_-]*$. Used as both the directory under~/Code/and the Gitea repo name. If the invoker gives something invalid, normalize and confirm. - One-sentence description — used in the Gitea repo metadata and as the README's first line.
- Goals — what the project is trying to do. Bulleted is fine.
- Non-goals — what it explicitly is not trying to do.
- Audience — who this is for (just Eric, a small group, public users).
Batch B — structured choices (use AskUserQuestion):
- Language: Node/TypeScript, Python, None / plain text.
- Visibility: Private (Recommended), Public.
- Gitea workflow stub: No (Recommended), Yes — stub
.gitea/workflows/ci.yml.
Capture the answers verbatim where possible — especially goals and non-goals. Do not paraphrase or editorialize.
Step 2 — Check the local path
If ~/Code/<name> already exists, stop. Tell the invoker and let them pick another name or remove the directory themselves. Never overwrite.
Step 3 — Scaffold the files
Create ~/Code/<name>/ and write the files below. Unix line endings, no trailing whitespace, no emojis.
README.md
# <name>
<one-sentence description>
## Goals
<goals — verbatim>
## Non-goals
<non-goals — verbatim>
## Audience
<audience — verbatim>
No badges, no install section, no license blurb.
CLAUDE.md
Orientation for future Claude sessions. Sections:
- What this is — one paragraph: name, description, why it exists.
- Audience — who it's for.
- Goals / Non-goals — verbatim from interrogation.
- Repository layout — describe what was actually scaffolded (don't list things that weren't created).
- Conventions — text-driven content; decisions, state, and stray thoughts logged in
docs/JOURNAL.mdas timestamped stream-of-thought entries (newest first, append-only, no titles, no template — tags[name](tag://name)go directly under the header only when a coherent theme emerges); research notes indocs/research/; product requirement docs indocs/prds/; low dependencies by default; ask before adding new tools. - When you're unsure — final line: "Ask. Default to drafting in chat over editing files when the request is ambiguous."
.gitignore
Always include OS junk:
.DS_Store
Thumbs.db
Plus, by language:
- Node/TS:
node_modules/,dist/,.env,.env.*,*.log - Python:
__pycache__/,*.pyc,.venv/,dist/,build/,*.egg-info/,.env - None: nothing extra.
docs/INDEX.md
Short pointer file. Two sentences max: "Decisions and state changes are logged in JOURNAL.md. Research notes live in research/."
docs/prds/.gitkeep
Empty file so the directory is tracked.
docs/research/.gitkeep
Empty file so the directory is tracked.
docs/JOURNAL.md
A running log of decisions, state changes, and stray thoughts. Newest entries on top, append-only, unstructured prose that captures what I was thinking at that point in time. Each entry: a timestamp heading (## YYYY-MM-DD HH:MM) followed by freeform body. No title, no template, no required sections. Tags optional — formatted as [name](tag://name) markdown links on the line directly under the header, only when a coherent theme has emerged.
Seed it with just the header and a one-line note describing the convention, so future-Eric (and future Claude, and the init-entry skill) know the format without opening this file:
# Journal
Append-only stream of thought. Newest entries on top. Each entry is a timestamp
followed by freeform prose. Tag entries with `[name](tag://name)` links under
the header — only when a coherent theme emerges. Otherwise just write.
.claude/skills/init-entry/SKILL.md
Copy the global init-entry skill into the project so /init-entry is available within this repo without depending on the user's global skill set, and so the journal convention is pinned at scaffold time:
mkdir -p ~/Code/<name>/.claude/skills/init-entry
cp ~/.claude/skills/init-entry/SKILL.md ~/Code/<name>/.claude/skills/init-entry/SKILL.md
If ~/.claude/skills/init-entry/SKILL.md doesn't exist on this machine, skip the copy and surface a warning in Step 7 — don't block the rest of the scaffold.
Language floor (minimal, no dependencies)
- Node/TypeScript:
package.jsonwith onlyname,version: "0.0.0",description,private: true. No scripts, no deps, notsconfig.json. The invoker adds those when they need them. - Python:
pyproject.tomlwith a minimal[project]table —name,version = "0.0.0",description. No build backend, no deps. The invoker chooses Hatch / Poetry / uv / setuptools when they're ready. - None: skip.
.gitea/workflows/ci.yml (only if invoker said yes)
name: ci
on: [push, pull_request]
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: echo "hello from ${GITHUB_REPOSITORY:-$GITEA_REPOSITORY}"
Step 4 — Initial commit
Run, against ~/Code/<name>:
git init -b main
git add -A
git commit -m "Initial commit"
If git init -b main fails (older git), fall back to git init && git checkout -b main before staging.
Step 5 — Create the Gitea repo
Build the request body with jq -n so quotes and special characters in the description don't break the JSON. Send:
curl -fsSL -X POST "$GITEA_URL/api/v1/user/repos" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg name "<name>" \
--arg desc "<description>" \
--argjson private <true|false> \
'{name: $name, description: $desc, private: $private, default_branch: "main", auto_init: false}')"
Never echo, log, or write $GITEA_TOKEN anywhere. Not in error messages, not in commands you describe to the user, not in any scaffolded file.
If the API call fails:
- 409 Conflict — repo already exists on Gitea. Stop. Tell the invoker, suggest they pick a different name or delete the existing repo.
- 401 Unauthorized — token is wrong, revoked, or lacks scope. Stop.
- 422 Unprocessable — name violates Gitea's rules. Stop.
- Anything else — surface the response body and stop.
In every failure case the local repo is already committed and intact. Do not delete it.
Step 6 — Add remote and push
Prefer SSH for the remote. Parse ssh_url from the API response (jq -r '.ssh_url') — falls back to clone_url (HTTPS) only if ssh_url is missing or empty. Then:
git -C ~/Code/<name> remote add origin <ssh_url>
git -C ~/Code/<name> push -u origin main
SSH avoids embedding tokens in .git/config and matches the invoker's setup (key-based auth to the Gitea host). If the push fails on auth, surface the exact git error — do not fall back to HTTPS-with-token as a workaround, since that leaks credentials into .git/config.
Step 7 — Report back
One short summary:
- Local path:
~/Code/<name> - Gitea URL:
html_urlfrom the API response. - What was scaffolded (language floor: yes/no/which; CI stub: yes/no; init-entry skill copied: yes/skipped-with-reason).
- Suggested next step: open
CLAUDE.mdand confirm goals / non-goals / audience read correctly.
Stop there. Don't volunteer to add a license, tests, dependencies, a src/ directory, or anything else unless the invoker asks.
Hard rules
- Never write the Gitea token to a file. Not
.env, notCLAUDE.md, not thegit remoteURL, not anywhere. It only lives in env. - Never overwrite an existing
~/Code/<name>. Bail with a clear error. - Never push if the Gitea API call failed. Local repo stays; let the invoker decide what to do next.
- No frameworks, no deps, no scripts, no
src/. This skill scaffolds a floor. Tooling comes later, deliberately. - No license file by default. Licensing is a deliberate choice; the invoker adds it when they want it.
- No tests, no CI beyond the optional stub the invoker explicitly opts into.
- Don't paraphrase goals, non-goals, or audience. Capture what the invoker said. They're the rubric, not a draft.
- Owner is always
didericis. Do not ask, do not parameterize.