| name | init-makefiles |
| description | Use if scaffolding Makefile targets for dev, tunnels, deploys, R2, Supabase, Railway, Vercel. |
init-makefiles
Make is the project's control plane. Generate one Makefile per app (root + up to 3 sub-Makefiles in monorepos), zero required arguments, scenario-appropriate targets only, with safe replacement of any prior scaffold.
This is the consolidated successor to the old make-local, make-railway, and make-vercel direction. Do not recreate provider-specific Makefile skills — classify the project's scenario here, then generate only the targets that scenario needs.
When to use this skill
Trigger on any of these:
- "set up make for this project" / "scaffold a Makefile" / "init Makefile targets"
- "add a make deploy target" / "wire deploy via make" / "give me
make local and make prod"
- "my
make local is broken / port-stuck / out of date" — refresh stale targets
- "this monorepo has three different Makefiles" — unify them under one banner
- "ship this Mac app to my macbook over rsync" — Scenario G
- "add
make tunnel / make funnel" for Tailscale exposure
- "sync the AGENTS.md make-targets section" after editing the Makefile
- "generate the GitHub Actions deploy workflow that backs
make deploy"
Do NOT use this skill when:
- The user wants a one-off shell script — use bash, not make
- Railway logs, scale, restart, env edits, or ad-hoc CLI ops are the job → use
run-railway
- The job is broad AGENTS hierarchy / REVIEW.md / folder-scoped agent config → use
init-agent-config
- The job is hosted-MCP architecture beyond a local MCP control target → use
build-mcp-use-server
Three principles (apply without exception)
- Scenarios beat providers. Don't ask "is this a Vercel project?" — ask "what is this project?" The provider stack (Vercel, Railway, Supabase) falls out of the scenario, never the other way around.
- Local = real dev builds, never toy builds. Next.js / Turborepo / monorepo projects use the actual dev pipeline. No shortcuts that diverge from production behavior.
- Exposure is opt-in.
make local binds 127.0.0.1. make local-lan binds 0.0.0.0. make tunnel runs Tailscale Serve (tailnet-only). make funnel is PUBLIC and gated behind explicit PUBLIC_FUNNEL=1. Funnel never enables as a side effect of any other command.
Hard rules baked into every generated Makefile
- Max 4 Makefiles per project — root + up to 3 app sub-Makefiles. If a monorepo has more apps, ask which three.
- Every target works with zero arguments. Env vars are overridable (
make local PORT=4000) but defaults must work. Sole exception: make supabase-migrate-new name=<n>.
- Universal preamble —
SHELL := bash, .SHELLFLAGS := -eu -o pipefail -c, .ONESHELL:, .DELETE_ON_ERROR:, MAKEFLAGS += --warn-undefined-variables --no-builtin-rules, .DEFAULT_GOAL := help. Full preamble in references/makefile-base.md.
- Port hygiene mandatory — use the
_free-port-% helper from references/port-hygiene.md. Only kill our own dev processes (node|next-server|turbo|turbopack|next|bun|deno); refuse foreign holders, suggest +10 port.
- Localhost binding by default. LAN binding via
make local-lan. Tailscale Serve via make tunnel (tailnet-only). Tailscale Funnel via make funnel (PUBLIC, opt-in only).
- Helper targets prefix with
_ (_check-vercel-tokens, _free-port-%, _print-banner-*). They do not appear in make help.
- Replace, don't edit. Never edit a Makefile incrementally — preview, manifest, snapshot, delete, regenerate. The targeted snapshot commit is the safety net.
Scenario classification
Pick exactly one scenario per deployable app. The seven:
| # | Scenario | Primary signal | What's in scope |
|---|
| A | Frontend-only | next.config.* / vite.config.* / etc.; no backend dir; no Supabase | Local, LAN, tunnel; Vercel deploy by default. Cloudflare Pages is opt-in only — switch to it when (and only when) the user explicitly says "Cloudflare", "Pages", "Wrangler", or names the Pages product. Disk signals alone (a stray wrangler.toml for R2/Workers) never flip the default. |
| B | MCP server | @modelcontextprotocol/sdk or mcp-use in deps | Local; no deploy; inspect target |
| C | Frontend + backend | A signals + apps/api (or similar) with Express/Hono/Fastify | Frontend + Vercel + Railway |
| D | Frontend + Supabase | A signals + @supabase/supabase-js + supabase/ dir | Frontend + Vercel + Supabase CLI ops |
| E | Multi-service Railway | Multiple service dirs / railway.toml files | Railway only; parallel deploy where safe |
| F | Build-artifact | Cargo / Go / Swift / native CLI; no HTTP framework, no remote target | Build + run locally; no deploy |
| G | MacBook ship | .xcodeproj / Package.swift Mac target + remote-Mac SSH alias | Build + ship via rsync + atomic swap + verify |
For single-app projects, the final classification has one tag. For monorepos, each deployable app gets one tag and the root gets a dominant orchestration tag. Use the supported combined tag C+D only when a custom backend deploys separately and Supabase is also in scope.
If detection is ambiguous → ask one targeted question, never guess. Sample disambiguation prompts in references/scenario-detection.md.
Workflow
1. Inspect (read-only)
Capture the project's state before writing anything:
ls bun.lockb pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null
ls next.config.* vite.config.* astro.config.* nuxt.config.* svelte.config.* remix.config.* 2>/dev/null
ls supabase/config.toml 2>/dev/null
jq -r '.dependencies | keys[]?' package.json 2>/dev/null | grep -i supabase
ls turbo.json pnpm-workspace.yaml apps/ packages/ 2>/dev/null
jq -r '.workspaces' package.json 2>/dev/null
find apps server services -maxdepth 2 -name "package.json" 2>/dev/null | head
jq -r '.dependencies | keys[]?' package.json 2>/dev/null | grep -E 'modelcontextprotocol|mcp-use'
ls *.xcodeproj *.xcworkspace Package.swift Info.plist 2>/dev/null
ls Makefile *.mk scripts/dev.sh scripts/deploy.sh 2>/dev/null
find . -maxdepth 3 -name "Makefile" -not -path "./node_modules/*"
ls -la AGENTS.md CLAUDE.md 2>/dev/null
Then run the bundled detector for a second, low-freedom signal pass. Resolve scripts/scenario-detect.sh relative to this skill directory and pass the downstream project root:
bash scripts/scenario-detect.sh /path/to/project
The detector is read-only and heuristic-only; it prints observed signals and candidate scenarios, not the final answer. Full detection cheat sheet in references/scenario-detection.md; detector implementation notes in scripts/scenario-detect.md.
If a precondition fails (no package.json, no dev script, no SSH alias for Mac scenario), surface it before generating. Do not generate something that breaks on first run.
2. Classify and announce
Before generating files, print this block:
Scenario: <A-G, or C+D for supported combined shape>
Scope: <repo root or per-app paths>
Confidence: <high|medium>
Signals: <observed files/deps/commands>
Excluded scenarios: <why the near misses were rejected>
Provider scope: <Vercel/Railway/Supabase/MacBook/local-only>
Makefiles to generate: <root + app paths, max 4>
Ambiguity resolved: <none or the answered question>
3. Preview and read existing scaffolding
Run the bundled wipe preview before touching files. Resolve scripts/preview-makefile-wipe.sh relative to this skill directory and pass the downstream project root:
bash scripts/preview-makefile-wipe.sh /path/to/project
bash scripts/preview-makefile-wipe.sh /path/to/project --paths-only > /tmp/init-makefiles-candidates.txt
The script is read-only. It prints exact paths, match reasons, tracked state, and uncommitted status; implementation notes live in scripts/preview-makefile-wipe.md.
For every candidate Makefile, *.mk, make-*.sh, scripts/dev.sh, and scripts/deploy.sh: read the file before classifying it as scaffold. Note useful patterns the user encoded (custom env handling, project-specific port choices, framework-specific dev commands) — those inform regenerated targets.
Write a deletion manifest containing only paths approved for replacement:
DELETION_MANIFEST=/tmp/init-makefiles-delete-manifest.txt
Refuse to continue if a candidate has uncommitted non-scaffold edits the agent cannot classify. If unrelated dirty files exist, leave them untouched and state that they were not snapshotted.
4. Snapshot and wipe from manifest
Create a targeted snapshot commit before deletion. Stage only manifest paths:
git status --porcelain
while IFS= read -r path; do
[ -n "$path" ] || continue
git add -- "$path"
done < "$DELETION_MANIFEST"
git commit -m "chore(make): snapshot existing scaffold before init-makefiles regen"
Never use git add -A for the snapshot. Never auto-commit unrelated work.
Print recovery before deleting:
SNAPSHOT_SHA=$(git rev-parse --short HEAD)
printf 'Recovery: git revert %s\n' "$SNAPSHOT_SHA"
printf 'Single file: git restore --source=%s -- path/to/Makefile\n' "$SNAPSHOT_SHA"
Delete only manifest paths:
while IFS= read -r path; do
[ -n "$path" ] || continue
rm -f -- "$path"
done < "$DELETION_MANIFEST"
5. Generate core Makefiles
Compose the new Makefile(s) from the scenario's references:
- Universal preamble + ANSI palette + helper conventions:
references/makefile-base.md (every Makefile uses this)
- Scenario A / C / D frontend targets (default for Scenario A):
references/makefile-frontend.md (local, local-lan, tunnel, deploy-vercel, verify, env-pull, build-check)
- Scenario A — Cloudflare Pages variant (opt-in only, when the user explicitly mentions Cloudflare / Pages / Wrangler):
references/makefile-cloudflare-pages.md (deploy-cloudflare, cf-project-init, cf-list, cf-tail, _redirects, Pages 25 MiB/file + 20k-file limits)
- R2 media bucket — single-file ops, CORS, hardlink staging, rclone bulk copy (≤20 transfers), wrangler-OAuth fallback:
references/makefile-r2-bulk.md (orthogonal — works regardless of whether the frontend is hosted on Vercel or Pages, opt-in when R2 is part of the workflow)
- Scenario C / E backend targets:
references/makefile-backend.md (Railway deploy, multi-service parallel, healthcheck rules, railway.toml baseline)
- Scenario D Supabase targets:
references/makefile-supabase.md (supabase-link, -migrate-*, -functions, -types, -secrets-*)
- Scenario G ship pipeline:
references/makefile-macbook.md (preflights, rsync, atomic swap, kill-then-launch, verify)
- Monorepo delegation and 4-file ceiling:
references/makefile-monorepo.md
Always pull tunnel/funnel rules from references/tailscale-funnel-rules.md (macOS DNS quirks, Funnel port restrictions, ACL preflight) and port-kill behavior from references/port-hygiene.md (kill-only-our-own pattern, default-port squat list, SIGTERM-then-SIGKILL escalation).
Env conventions per scenario live in references/env-vars-conventions.md (.env.local vs .env.railway, Vercel sensitivity defaults, Railway built-in vars).
6. Update AGENTS.md / CLAUDE.md
Apply the 5-state machine in references/agents-md-update.md. The contract:
- AGENTS.md is the source of truth
- CLAUDE.md is a symlink → AGENTS.md (or absent)
- Only the downstream project's
## Make targets section is written/updated, listing every generated target, env-var knob, and what the skill will and will not do
- Same-directory
CLAUDE.md → AGENTS.md compatibility links are in scope
- Hand-written
## Make targets sections require explicit replacement consent
- Skip the rewrite if the existing section is already accurate (no churn)
- Broad AGENTS-first governance, nested folder instructions, and REVIEW.md remain the job of
init-agent-config
7. Verify core generation
Run the verification ladder in references/verification-ladder.md. The skill claims at most rung 5 (external curl from this machine, no proxy). Rung 6 (independent client — phone on cellular) is a manual user step printed in the post-deploy banner.
Per scenario:
- A / C / D:
make local opens browser; make deploy returns Ready URL; make verify HTTP-probes
- B:
make local starts MCP on 127.0.0.1:<port>; make inspect returns tool schema
- E:
make deploy-all deploys services in parallel; make verify HTTP-probes each
- F:
make build produces a binary; ./build/<name> --version runs without error
- G:
make ship rsyncs and pgrep -x "$(APP_NAME)" returns a PID after sleep 2
Core generation is complete when Makefiles, AGENTS.md sync, and verification are done. The skill remains useful and complete if CI/CD is declined.
8. Optional — CI/CD
Ask first: "Generate GitHub Actions deploy wiring locally? (y/n)". If yes, follow references/ci-cd-workflow.md:
- Detect GitHub repo via
gh repo view --json nameWithOwner -q .nameWithOwner
- Generate
.github/workflows/deploy.yml for the providers in scope only
- Prompt user for ONLY the tokens needed (Vercel / Railway / Supabase)
gh secret set <NAME> --body "$pasted" for each required secret
- Verify required secrets by name with
gh secret list
- Commit the workflow locally
- Ask for explicit push authorization
- Push only after that authorization; never force-push
If declined, leave .github/workflows/ untouched. No half-baked YAML.
Decision rules
- Localhost-only by default.
make local binds 127.0.0.1. LAN exposure must be explicit (make local-lan).
- Funnel never auto-enables.
make funnel is the only target that uses Funnel, and only with PUBLIC_FUNNEL=1.
- Backends don't run locally by default. Local hosts the frontend or an MCP server. Backend stays remote in dev unless
LOCAL_BACKEND=1 is set explicitly.
- Provider scope is conditional. Vercel only if there's a frontend. Railway only if there's a custom backend. Supabase only if
supabase/ exists. MacBook only if a Mac project + remote-Mac target.
- No improvised commands. Inspect first; pick the canonical command from the relevant reference.
- Replace before regenerating. Preview, read, manifest, targeted snapshot, print recovery, then delete only manifest paths. Never edit Makefiles incrementally.
- Max 4 Makefiles. Ask if there are more apps than slots.
- Detection ambiguous → ask one question, never guess.
Recovery from wipe
git log --oneline -- Makefile
git show --stat <snapshot-sha>
git revert <snapshot-sha>
git restore --source=<snapshot-sha> -- path/to/Makefile
Failure modes (and where to fix them)
| Symptom | Cause | Fix |
|---|
make local fails with "port :PORT held by orbstack" | Foreign port holder; refused per port-hygiene rule | Pick a different port (make local PORT=4000); don't kill OrbStack |
make tunnel reports "tailscale not signed in" | Tailscale daemon up but account not linked | tailscale up interactively; this skill assumes Tailscale is signed in |
make deploy-vercel fails with "Project not found" | .vercel/project.json missing or wrong project name | Re-run vercel link --yes --project <name> --token=$VERCEL_TOKEN |
make deploy-railway returns 502 "Application Failed to Respond" | App listens on localhost:3000 not 0.0.0.0:$PORT | Fix app's bind address; redeploy |
make supabase-migrate-apply no-ops with "no migrations to apply" | Migration files already in remote history | Run make supabase-pull to detect drift |
make ship fails preflight with "ssh alias not found" | Host macbook not in ~/.ssh/config | Add the Host block; make ship re-checks |
| AGENTS.md and CLAUDE.md both exist with different content | State 1 of the symlink machine | Skill prompts user; merges into AGENTS.md per references/agents-md-update.md |
gh secret set fails with "auth required" | Not authenticated | gh auth login; CI/CD step re-runs |
| Vercel build fails with function over 250 MB | Heavy bundled deps | make build-check surfaces sizes; references/makefile-frontend.md lists mitigations |
make funnel rejects port 3000 | Funnel allows only 443/8443/10000 | Use make funnel TUNNEL_PORT=443 (and PUBLIC_FUNNEL=1) |
make tunnel works but host <node>.<tailnet>.ts.net fails on macOS | macOS DNS quirk: host/nslookup/bare dig bypass system resolver | Use tailscale dns query <fqdn> or dscacheutil -q host -a name <fqdn> |
Final report contract
Return this shape when the downstream project work is done:
Scenario: <chosen scenario and confidence>
Generated files: <paths>
Deleted/replaced scaffold paths: <paths or none>
Snapshot: <sha and recovery command>
AGENTS.md / CLAUDE.md: <state>
CI/CD: <skipped | generated locally | secrets wired | pushed>
Verification: <rung actually reached per target>
Manual verification still required: <targets, especially rung 6>
Reference routing
| File | Read when |
|---|
references/scenario-detection.md | Classifying a project; resolving ambiguity; sample disambiguation prompts |
references/makefile-base.md | Universal preamble, ANSI palette, helper conventions; every Makefile uses this |
references/makefile-frontend.md | Generating Scenarios A / C / D frontend targets (local, local-lan, tunnel, deploy-vercel, verify, env-pull, build-check) |
references/makefile-cloudflare-pages.md | Scenario A Cloudflare Pages variant — opt-in only. Read this reference only when the user explicitly mentions Cloudflare / Pages / Wrangler. The Scenario A default is Vercel (makefile-frontend.md). Contents: deploy-cloudflare, cf-project-init, cf-list, cf-tail, wrangler.toml shape, _redirects proxying, 25 MiB-per-file / 20k-file limit checks. |
references/makefile-r2-bulk.md | Cloudflare R2 media (opt-in when R2 is part of the workflow; works with either Vercel or Pages-hosted frontends): r2-info, r2-cors-apply, single-file r2-put/r2-get/r2-rm, hardlink r2-stage, rclone-configure/rclone-check, r2-sync (rclone copy — additive default for mixed-tenant safety), wrangler-OAuth bulk fallback, public-access audit |
references/makefile-backend.md | Generating Scenarios C / E backend targets (Railway deploy, multi-service parallel, healthcheck rules, railway.toml baseline) |
references/makefile-supabase.md | Generating Scenario D Supabase targets (supabase-link, -migrate-*, -functions, -types, -secrets-*) |
references/makefile-macbook.md | Generating Scenario G ship pipeline (preflights, rsync, atomic swap, kill-then-launch, verify) |
references/makefile-monorepo.md | Multi-Makefile delegation; 4-file ceiling; per-app namespacing; root-Makefile $(MAKE) -C pattern |
references/tailscale-funnel-rules.md | Tunnel/Funnel target generation; macOS DNS quirks; Funnel port restrictions; ACL preflight |
references/port-hygiene.md | The kill-only-our-own pattern; default-port squat list; SIGTERM-then-SIGKILL escalation; banner conventions |
references/agents-md-update.md | The 5-state machine for AGENTS.md / CLAUDE.md; the ## Make targets section template; idempotency rule |
references/ci-cd-workflow.md | Wiring GitHub Actions; gh secret set sequence; concurrency rules; rotation hint |
references/env-vars-conventions.md | Where envs live per scenario; .env.local vs .env.railway; Vercel sensitivity defaults; Railway built-in vars |
references/verification-ladder.md | The 6 rungs; per-target verification; banner template for the user's manual rung-6 step |
scripts/scenario-detect.md | Read-only heuristic detector script; use before final classification and ambiguity questions |
scripts/preview-makefile-wipe.md | Read-only wipe preview script; use before manifest, targeted snapshot, and deletion |
Cross-skill handoffs
run-railway — for ad-hoc Railway CLI ops not covered by deploy targets (logs, scale, restart, env management beyond what the Makefile exposes)
build-mcp-use-server — for hosted-MCP scenarios beyond local-only MCP servers (Scenario B is for local-facing MCP only)
- Mac-app development standards (framework choice, SwiftUI patterns) are out of scope — this skill only does Make scaffolding.
init-agent-config — for AGENTS hierarchy, REVIEW.md, folder-scoped agent config, and broad repo governance
Guardrails
- Never enable Funnel as a side effect. Funnel is never in
make tunnel. Funnel only in make funnel with PUBLIC_FUNNEL=1 ack.
- Never run
tailscale serve reset / tailscale funnel reset without explicit user consent. They wipe ALL mappings on this node — other projects' included. Use targeted disable (tailscale serve --https=443 <port> off) instead.
- Never
kill -9 foreign processes on a port. Port hygiene refuses, suggests +10.
- Never generate Railway config in projects without a custom backend. Skip Scenarios A / B / D / F / G.
- Never generate Vercel config in projects without a frontend. Skip Scenarios E / F / G.
- Default Scenario A to Vercel. Cloudflare Pages is opt-in: switch only when the user explicitly names Cloudflare / Pages / Wrangler / Workers. Disk signals are NOT enough —
wrangler.toml commonly exists alongside Vercel-deployed frontends to drive R2 or Workers, with the frontend itself still on Vercel.
- Never generate both Vercel and Cloudflare Pages deploy targets in the same Makefile. Pick one — having both makes
make deploy ambiguous. The user's stated intent decides; signals only inform the question to ask.
- Never default
make r2-sync to rclone sync. R2 buckets are often multi-tenant; sync would delete other projects' data. Default to rclone copy (additive). See references/makefile-r2-bulk.md.
- Never blanket-derive an R2 S3 Access Key ID from a
cfat_* / cfut_* token. They are independent values — the dashboard issues both at token creation. rclone / aws-cli need the AKID + Secret pair, not the cfat_/cfut_ value. See references/makefile-r2-bulk.md.
- Never declare an R2 upload complete without auditing the bucket for publicly-readable secrets. Custom-domain binding makes every key reachable. Probe likely-leak paths (
.secrets/, .env, id_rsa, credentials.json) after first sync; surface anything 200 to the user.
- Never generate Supabase migrations in projects without
supabase/. Skip Scenarios A / B / C / E / F / G.
- Never write tokens to disk except as
gh secret set payloads. Never echo tokens back to the user.
- Never edit a Makefile in place. Manifest, snapshot, delete, and regenerate. The targeted snapshot commit is the safety net.
- Never delete scaffold paths that were not printed in the preview and copied into the deletion manifest.
- Never push CI/CD workflow commits without explicit push authorization after local generation and secret verification.
- Never set
output: 'standalone' in next.config.js for Vercel-hosted Next.js. Standalone is for self-host (Railway/Docker) only.
- Never use
host / nslookup / bare dig to verify MagicDNS names on macOS — they bypass the system resolver. Use tailscale dns query <fqdn> or dscacheutil -q host -a name <fqdn>.
- Never use
RAILWAY_API_TOKEN (account scope) when a project token would do. CI uses project tokens.
- Never blanket-import
.env.local into Vercel production. Dev secrets leak that way.
Final checks
Before declaring done: