670e253584
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
223 lines
9.7 KiB
Markdown
223 lines
9.7 KiB
Markdown
---
|
|
name: init-project
|
|
description: 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:
|
|
|
|
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/<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.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/<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.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/<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_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/<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.
|