Scaffold a new GitHub App in this monorepo from the checked-in template. Takes a description of the new app as its argument. Use when asked to "create a new app", "scaffold a new GitHub App", "add an app that <does X>".
Resources
1Install
npx skillscat add bostonaholic/github-apps/create-github-app Install via the SkillsCat registry.
Create a new GitHub App from the template
Scaffolds one new app directory from this skill's bundledresources/template/. The
copy and token fill are mechanical; app.yml, README.md, AGENTS.md,
and the src/ design take judgment, driven by the description. One
argument:
- description — what the new app should do, in the user's words.
Missing, or too vague to derive a name and behavior from → ask; never
guess.
Scope ends at a verified, ready-to-commit scaffold. Registration,
hosting, icons, and e2e come later (Step 8).
Hard constraints — read first
- Never modify
resources/template/or another app's directory during
a scaffold; every write lands under the newapps/<app>/or the shared
registration files (rootREADME.mdand.github/dependabot.yml, both
Step 6). - Never register, deploy, install, or repoint webhooks — out of scope.
npm installneeds network; offline → stop after Step 4 and say so.- If verification fails, the scaffold is wrong — fix it; never skip or
weaken tests to get past the gate.
Step 1 — derive and confirm the name
Derive a kebab-case app name from the description. Rules:
- lowercase
a-z, digits, and hyphens; starts with a letter; no
leading/trailing/double hyphen - at most 21 characters — the registered name
<app>-bostonaholic
must fit GitHub's 34-character app-name limit (-bostonaholicis 13) test -d apps/<app>at the repo root must fail; an existing directory of
that name → stop and tell the user
Also derive the description phrase: a lowercase verb phrase completing
"a GitHub App that …" — no trailing period, no " or \ (it lands
inside a JSON string).
Present the name and the phrase to the user and wait for confirmation
before writing anything — the name becomes a GitHub slug, a hostname,
and a tag prefix, so renaming later is painful.
Step 2 — copy
From the repo root:
cp -R .claude/skills/create-github-app/resources/template/ apps/<app>/
rm -f apps/<app>/TEMPLATE.md # -f: an `rm -i` alias would silently skip it
test ! -e apps/<app>/TEMPLATE.md && test -L apps/<app>/CLAUDE.md \
&& test -L apps/<app>/GEMINI.md && test -x apps/<app>/e2e.shThe test line guards against a skipped TEMPLATE.md deletion and a
copy that dereferenced the CLAUDE.md/GEMINI.md symlinks or droppede2e.sh's exec bit.
Step 3 — mechanical fill
APP='<app>' DESC='<phrase>' find apps/<app> -type f -exec perl -pi -e \
's/__APP_NAME__/$ENV{APP}/g; s/__APP_DESCRIPTION__/$ENV{DESC}/g' {} +
grep -rn '__APP_' apps/<app>/ # gate: must print nothingperl -pi sidesteps the BSD/GNU sed -i split; passing the values via
the environment keeps any regex metacharacters in the phrase inert;-type f skips the symlinks so AGENTS.md is rewritten once. A grep
hit means a file was missed or a token was mistyped — find it before
continuing.
Step 4 — judgment fill
Clear every TODO(scaffold) marker; grep -rn 'TODO(scaffold)' apps/<app>/
must end up empty. From the description:
app.yml— the minimum viabledefault_eventsanddefault_permissions, one justifying comment per permission (style:
the other apps'app.yml).src/— pure core module(s) with real signatures, an
orchestration module, andindex.tswiring that matchesapp.yml
exactly. Wiring stays logic-free. Rename or extendsrc/config.tsas
the design dictates. Relative imports use the.jsextension.test/— one test file per src module (minusindex.ts);
hand-written structural fake octokits, no mocking framework.README.md— pitch and behavior sections; the Setup permissions
list must mirrorapp.ymlexactly.AGENTS.md— a bullet per src module and the app-specific
behavioral invariants.
Step 5 — verify
cd apps/<app> && npm install && npm run typecheck && npm test && npm run buildAll green before proceeding (see Hard constraints).
Step 6 — register in docs and Dependabot
- Add the app's row (alphabetical) to the
## Appstable in the rootREADME.md. - Add an
npmentry for/apps/<app>to.github/dependabot.yml,
copying an existing entry and swapping the directory andchore(<app>)commit scope. The root is not a package, so there is no/entry; the template under.claude/is deliberately not scanned.
Step 7 — propose the commit
feat(<app>): scaffold, covering apps/<app>/ (including the generatedpackage-lock.json), the root README row, and the .github/dependabot.yml
entry. Propose it; don't commit without approval.
Step 8 — report follow-ups
End by telling the user what was scaffolded and what intentionally
remains (don't attempt these):
- Register the app — Setup §1 of
apps/<app>/README.md(Probot setup flow). - Install it on repos — the
install-github-appskill; add itsapps/<app>.mdresource — the file that sits next toinstall-github-app's SKILL.md, not under the repo's top-levelapps/
— per that skill's "Adding a new app" section. - Vercel project + DNS —
apps/<app>/README.md, "Deploy". - Icons —
apps/<app>/assets/; then copyicon.svg→public/favicon.svg
and generatepublic/favicon.png(sips -Z 64 assets/icon.png --out public/favicon.png) — the page already links both, and Vercel's
dashboard takes its project icon from the deployed favicon. - Real e2e —
e2e.sh, a sandbox repo, and ane2e-<app>skill
(models: the existing apps). Set the sandbox repo's watch status to
Ignore (repo page → Watch → Ignore): e2e churn — PRs, comments,
and any deliberately failing workflow runs — otherwise emails
whoever triggers it, and GitHub has no per-repo Actions notification
setting.