--- name: init-project description: Scaffold a new personal project at ~/Code/, 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: 1. Verify both `GITEA_URL` and `GITEA_TOKEN` are 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 with `write:repository` scope. 2. Verify `~/Code` exists; create it if not. 3. 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/` 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//` and write the files below. Unix line endings, no trailing whitespace, no emojis. ### `README.md` ``` # ## Goals ## Non-goals ## Audience ``` 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.md` as 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 in `docs/research/`; product requirement docs in `docs/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//.claude/skills/init-entry cp ~/.claude/skills/init-entry/SKILL.md ~/Code//.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.json` with only `name`, `version: "0.0.0"`, `description`, `private: true`. No scripts, no deps, no `tsconfig.json`. The invoker adds those when they need them. - **Python:** `pyproject.toml` with 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) ```yaml 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/`: ``` 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 "" \ --arg desc "" \ --argjson private \ '{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/ remote add origin git -C ~/Code/ 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/` - Gitea URL: `html_url` from 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.md` and 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`, not `CLAUDE.md`, not the `git remote` URL, not anywhere. It only lives in env. - **Never overwrite an existing `~/Code/`.** 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.